From 9d35bf7b718a5d1e6d65b82354f5f53ec267499d Mon Sep 17 00:00:00 2001 From: scottf Date: Tue, 13 Jan 2026 19:32:40 -0500 Subject: [PATCH] Fully 21 and Gradle 9 --- .github/workflows/build-main.yml | 21 +- .github/workflows/build-pr.yml | 21 +- .github/workflows/build-release.yml | 16 +- build.gradle | 123 +- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45633 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +- gradlew.bat | 3 +- settings.gradle | 11 +- .../java/io/synadia/json/ArrayBuilder.java | 3 +- .../java/io/synadia/json/DateTimeUtils.java | 6 +- src/main/java/io/synadia/json/Encoding.java | 5 +- .../io/synadia/json/JsonParseException.java | 16 + src/main/java/io/synadia/json/JsonParser.java | 5 +- .../io/synadia/json/JsonSerializable.java | 5 +- src/main/java/io/synadia/json/JsonValue.java | 31 +- .../io/synadia/json/JsonValueSupplier.java | 3 +- .../java/io/synadia/json/JsonValueType.java | 3 +- .../java/io/synadia/json/JsonValueUtils.java | 3 +- .../java/io/synadia/json/JsonWriteUtils.java | 3 +- .../io/synadia/json/ListValueResolver.java | 3 +- src/main/java/io/synadia/json/MapBuilder.java | 3 +- .../java/io/nats/client/api/AckPolicy.java | 61 - .../java/io/nats/client/api/ApiResponse.java | 25 - .../java/io/nats/client/api/ClusterInfo.java | 78 - .../io/nats/client/api/CompressionOption.java | 51 - .../client/api/ConsumerConfiguration.java | 1424 ----------------- .../io/nats/client/api/ConsumerLimits.java | 71 - .../io/nats/client/api/DeliverPolicy.java | 55 - .../io/nats/client/api/DiscardPolicy.java | 51 - .../java/io/nats/client/api/External.java | 153 -- .../io/nats/client/api/LostStreamData.java | 69 - src/test/java/io/nats/client/api/Mirror.java | 31 - .../java/io/nats/client/api/MirrorInfo.java | 36 - .../java/io/nats/client/api/PeerInfo.java | 81 - .../java/io/nats/client/api/Placement.java | 148 -- .../io/nats/client/api/PriorityPolicy.java | 52 - .../java/io/nats/client/api/ReplayPolicy.java | 51 - src/test/java/io/nats/client/api/Replica.java | 41 - .../java/io/nats/client/api/Republish.java | 78 - .../io/nats/client/api/RetentionPolicy.java | 52 - .../java/io/nats/client/api/ServerInfo.java | 193 --- src/test/java/io/nats/client/api/Source.java | 34 - .../java/io/nats/client/api/SourceBase.java | 60 - .../java/io/nats/client/api/SourceInfo.java | 38 - .../io/nats/client/api/SourceInfoBase.java | 86 - .../java/io/nats/client/api/StorageType.java | 42 - .../io/nats/client/api/StreamAlternate.java | 67 - .../nats/client/api/StreamConfiguration.java | 819 ---------- .../java/io/nats/client/api/StreamInfo.java | 125 -- .../java/io/nats/client/api/StreamState.java | 208 --- src/test/java/io/nats/client/api/Subject.java | 75 - .../io/nats/client/api/SubjectTransform.java | 74 - .../io/nats/client/support/ApiConstants.java | 222 --- .../io/nats/client/support/DateTimeUtils.java | 106 -- src/test/java/io/synadia/json/Benchmark.java | 190 --- .../synadia/json/BenchmarkUnitCoverage.java | 337 ---- .../io/synadia/json/DateTimeUtilsTests.java | 5 +- .../java/io/synadia/json/EncodingTests.java | 5 +- .../io/synadia/json/JsonParsingTests.java | 76 +- .../synadia/json/JsonUnicodeParsingTest.java | 5 +- .../io/synadia/json/JsonValueUtilsTests.java | 5 +- .../io/synadia/json/JsonWriteUtilsTests.java | 5 +- 63 files changed, 242 insertions(+), 5434 deletions(-) delete mode 100644 src/test/java/io/nats/client/api/AckPolicy.java delete mode 100644 src/test/java/io/nats/client/api/ApiResponse.java delete mode 100644 src/test/java/io/nats/client/api/ClusterInfo.java delete mode 100644 src/test/java/io/nats/client/api/CompressionOption.java delete mode 100644 src/test/java/io/nats/client/api/ConsumerConfiguration.java delete mode 100644 src/test/java/io/nats/client/api/ConsumerLimits.java delete mode 100644 src/test/java/io/nats/client/api/DeliverPolicy.java delete mode 100644 src/test/java/io/nats/client/api/DiscardPolicy.java delete mode 100644 src/test/java/io/nats/client/api/External.java delete mode 100644 src/test/java/io/nats/client/api/LostStreamData.java delete mode 100644 src/test/java/io/nats/client/api/Mirror.java delete mode 100644 src/test/java/io/nats/client/api/MirrorInfo.java delete mode 100644 src/test/java/io/nats/client/api/PeerInfo.java delete mode 100644 src/test/java/io/nats/client/api/Placement.java delete mode 100644 src/test/java/io/nats/client/api/PriorityPolicy.java delete mode 100644 src/test/java/io/nats/client/api/ReplayPolicy.java delete mode 100644 src/test/java/io/nats/client/api/Replica.java delete mode 100644 src/test/java/io/nats/client/api/Republish.java delete mode 100644 src/test/java/io/nats/client/api/RetentionPolicy.java delete mode 100644 src/test/java/io/nats/client/api/ServerInfo.java delete mode 100644 src/test/java/io/nats/client/api/Source.java delete mode 100644 src/test/java/io/nats/client/api/SourceBase.java delete mode 100644 src/test/java/io/nats/client/api/SourceInfo.java delete mode 100644 src/test/java/io/nats/client/api/SourceInfoBase.java delete mode 100644 src/test/java/io/nats/client/api/StorageType.java delete mode 100644 src/test/java/io/nats/client/api/StreamAlternate.java delete mode 100644 src/test/java/io/nats/client/api/StreamConfiguration.java delete mode 100644 src/test/java/io/nats/client/api/StreamInfo.java delete mode 100644 src/test/java/io/nats/client/api/StreamState.java delete mode 100644 src/test/java/io/nats/client/api/Subject.java delete mode 100644 src/test/java/io/nats/client/api/SubjectTransform.java delete mode 100644 src/test/java/io/nats/client/support/ApiConstants.java delete mode 100644 src/test/java/io/nats/client/support/DateTimeUtils.java delete mode 100644 src/test/java/io/synadia/json/Benchmark.java delete mode 100644 src/test/java/io/synadia/json/BenchmarkUnitCoverage.java diff --git a/.github/workflows/build-main.yml b/.github/workflows/build-main.yml index 8877b5f..318fb15 100644 --- a/.github/workflows/build-main.yml +++ b/.github/workflows/build-main.yml @@ -15,18 +15,25 @@ jobs: SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup java - uses: actions/setup-java@v4 + - name: Setup JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' java-version: '21' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + gradle-version: current + - name: Check out code + uses: actions/checkout@v4 - name: Build and Test - run: chmod +x gradlew && ./gradlew clean test jacocoTestReport coveralls + run: chmod +x gradlew && ./gradlew clean test jacocoTestReport - name: Verify Javadoc run: ./gradlew javadoc + - name: Send coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.COVERALLS_REPO_TOKEN }} - name: Publish Snapshot run: ./gradlew -i publishToSonatype diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 3b8ac45..3c25a38 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -9,16 +9,23 @@ jobs: runs-on: ubuntu-latest env: BUILD_EVENT: ${{ github.event_name }} - COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup java - uses: actions/setup-java@v4 + - name: Setup JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' java-version: '21' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + gradle-version: current + - name: Check out code + uses: actions/checkout@v4 - name: Build and Test - run: chmod +x gradlew && ./gradlew clean test jacocoTestReport coveralls + run: chmod +x gradlew && ./gradlew clean test jacocoTestReport - name: Verify Javadoc run: ./gradlew javadoc + - name: Send coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index e8a8b21..be90a0a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -17,16 +17,20 @@ jobs: SIGNING_KEY: ${{ secrets.SIGNING_KEY }} SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup java - uses: actions/setup-java@v4 + - name: Setup JDK + uses: actions/setup-java@v5 with: - distribution: 'temurin' java-version: '21' + distribution: 'temurin' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + gradle-version: current + - name: Check out code + uses: actions/checkout@v4 - name: Build and Test run: chmod +x gradlew && ./gradlew clean test - name: Verify Javadoc run: ./gradlew javadoc - name: Verify, Sign and Publish Release - run: ./gradlew -i signMavenJavaPublication publishToSonatype closeAndReleaseSonatypeStagingRepository + run: ./gradlew -i publishToSonatype closeAndReleaseSonatypeStagingRepository diff --git a/build.gradle b/build.gradle index e2a02dc..fbd8c18 100644 --- a/build.gradle +++ b/build.gradle @@ -1,43 +1,70 @@ -import org.gradle.internal.os.OperatingSystem +import aQute.bnd.gradle.Bundle plugins { - id 'java' - id 'java-library' - id 'maven-publish' - id 'jacoco' - id 'com.github.kt3k.coveralls' version '2.12.2' - id "org.gradle.test-retry" version "1.1.9" - id 'io.github.gradle-nexus.publish-plugin' version '2.0.0' - id 'signing' + id("java") + id("java-library") + id("maven-publish") + id("jacoco") + id("biz.aQute.bnd.builder") version "7.2.1" + id("org.gradle.test-retry") version "1.6.4" + id("io.github.gradle-nexus.publish-plugin") version "2.0.0" + id("signing") } def jarVersion = "3.0.0" group = 'io.synadia' -def isMerge = System.getenv("BUILD_EVENT") == "push" def isRelease = System.getenv("BUILD_EVENT") == "release" +def tc = System.getenv("TARGET_COMPATIBILITY"); +def targetCompat = tc == "25" ? JavaVersion.VERSION_25 : JavaVersion.VERSION_21 +def jarEnd = tc == "25" ? "-jdk25" : "" +def jarAndArtifactName = "jnats-json" + jarEnd -// version is the variable the build actually uses. -version = isRelease ? jarVersion : jarVersion + "-SNAPSHOT" +version = isRelease ? jarVersion : jarVersion + "-SNAPSHOT" // version is the variable the build actually uses. java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = targetCompat } repositories { mavenCentral() - maven { url "https://oss.sonatype.org/content/repositories/releases/" } - maven { url "https://repo1.maven.org/maven2/" } + maven { url="https://repo1.maven.org/maven2/" } + maven { url="https://central.sonatype.com/repository/maven-snapshots/" } } dependencies { implementation 'org.jspecify:jspecify:1.0.0' - implementation 'commons-codec:commons-codec:1.18.0' + implementation 'commons-codec:commons-codec:1.20.0' + + testImplementation 'org.junit.jupiter:junit-jupiter:5.14.1' + + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.register('bundle', Bundle) { + from sourceSets.main.output +} + +java { + withSourcesJar() + withJavadocJar() +} + +jar { + bundle { + bnd("Bundle-Name": "io.synadia.jnats.json", + "Bundle-Vendor": "synadia.io", + "Bundle-Description": "JNats Json Utils", + "Bundle-DocURL": "https://github.com/synadia-io/jnats.json", + "Target-Compatibility": "Java " + targetCompat + ) + } +} - testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1' - testImplementation 'nl.jqno.equalsverifier:equalsverifier:4.0' +java { + withSourcesJar() + withJavadocJar() } test { @@ -50,36 +77,11 @@ test { } javadoc { - options.addBooleanOption('html5', true) + failOnError = false options.overview = 'src/main/javadoc/overview.html' // relative to source root source = sourceSets.main.allJava title = "NATS.IO JNats JSON" classpath = sourceSets.main.runtimeClasspath - doLast { - if (!OperatingSystem.current().isWindows()) { - exec { - println "Updating favicon on all html files" - workingDir 'build/docs/javadoc' - // Only on linux, mac at this point - commandLine 'find', '.', '-name', '*.html', '-exec', 'sed', '-i', '-e', 's###', '{}', ';' - } - copy { - println "Copying images to javadoc folder" - from 'src/main/javadoc/images' - into 'build/docs/javadoc' - } - } - } -} - -tasks.register('javadocJar', Jar) { - archiveClassifier.set('javadoc') - from javadoc -} - -tasks.register('sourcesJar', Jar) { - archiveClassifier.set('sources') - from sourceSets.main.allSource } jacoco { @@ -93,19 +95,13 @@ jacocoTestReport { } } -artifacts { - archives javadocJar, sourcesJar -} - -if (isMerge || isRelease) { - nexusPublishing { - repositories { - sonatype { - nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) - snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) - username = System.getenv('OSSRH_USERNAME') - password = System.getenv('OSSRH_PASSWORD') - } +nexusPublishing { + repositories { + sonatype { + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) + username = System.getenv('OSSRH_USERNAME') + password = System.getenv('OSSRH_PASSWORD') } } } @@ -117,12 +113,12 @@ publishing { artifact sourcesJar artifact javadocJar pom { - name = rootProject.name + name = jarAndArtifactName packaging = 'jar' groupId = group - artifactId = archivesBaseName + artifactId = jarAndArtifactName description = 'JSON Parser built specifically for JNATS' - url = 'https://github.com/synadia-io/json.java21' + url = 'https://github.com/synadia-io/jnats.json' licenses { license { name = 'The Apache License, Version 2.0' @@ -138,7 +134,7 @@ publishing { } } scm { - url = 'https://github.com/synadia-io/json.java21' + url = 'https://github.com/synadia-io/jnats.json' } } } @@ -151,6 +147,7 @@ if (isRelease) { def signingKey = System.getenv('SIGNING_KEY') def signingPassword = System.getenv('SIGNING_PASSWORD') useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) + sign configurations.archives sign publishing.publications.mavenJava } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644 GIT binary patch delta 37442 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

WNp5~7YD4``2J+2^JLXSDAn1#GQa=Wm~d2R({ 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 delta 35599 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 ORzXA2f%7X@nSTIfr%7r6 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..23449a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9..adff685 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/settings.gradle b/settings.gradle index a75bfef..a3061a8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,10 +1 @@ -/* - * This file was generated by the Gradle 'init' task. - * - * The settings file is used to specify which projects to include in your build. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user manual at https://docs.gradle.org/6.3/userguide/multi_project_builds.html - */ - -rootProject.name = 'jnats-json-21' +rootProject.name = 'jnats-json' diff --git a/src/main/java/io/synadia/json/ArrayBuilder.java b/src/main/java/io/synadia/json/ArrayBuilder.java index a45ea93..054c426 100644 --- a/src/main/java/io/synadia/json/ArrayBuilder.java +++ b/src/main/java/io/synadia/json/ArrayBuilder.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/DateTimeUtils.java b/src/main/java/io/synadia/json/DateTimeUtils.java index 27ff9b2..4f89b75 100644 --- a/src/main/java/io/synadia/json/DateTimeUtils.java +++ b/src/main/java/io/synadia/json/DateTimeUtils.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2020-2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: @@ -11,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. + package io.synadia.json; import org.jspecify.annotations.NonNull; diff --git a/src/main/java/io/synadia/json/Encoding.java b/src/main/java/io/synadia/json/Encoding.java index f0d245e..6af4e44 100644 --- a/src/main/java/io/synadia/json/Encoding.java +++ b/src/main/java/io/synadia/json/Encoding.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2020-2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/JsonParseException.java b/src/main/java/io/synadia/json/JsonParseException.java index 6be503c..8cf216b 100644 --- a/src/main/java/io/synadia/json/JsonParseException.java +++ b/src/main/java/io/synadia/json/JsonParseException.java @@ -1,3 +1,19 @@ +// Copyright 2023 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package io.synadia.json; import java.io.IOException; diff --git a/src/main/java/io/synadia/json/JsonParser.java b/src/main/java/io/synadia/json/JsonParser.java index c25c8e7..2cbd4da 100644 --- a/src/main/java/io/synadia/json/JsonParser.java +++ b/src/main/java/io/synadia/json/JsonParser.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2023 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/JsonSerializable.java b/src/main/java/io/synadia/json/JsonSerializable.java index 5992f69..d514d53 100644 --- a/src/main/java/io/synadia/json/JsonSerializable.java +++ b/src/main/java/io/synadia/json/JsonSerializable.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2021-2023 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/JsonValue.java b/src/main/java/io/synadia/json/JsonValue.java index 0e17977..4c967e7 100644 --- a/src/main/java/io/synadia/json/JsonValue.java +++ b/src/main/java/io/synadia/json/JsonValue.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2023 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: @@ -504,17 +507,19 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = map != null ? map.hashCode() : 0; - result = 31 * result + (array != null ? array.hashCode() : 0); - result = 31 * result + (string != null ? string.hashCode() : 0); - result = 31 * result + (bool != null ? bool.hashCode() : 0); - result = 31 * result + (i != null ? i.hashCode() : 0); - result = 31 * result + (l != null ? l.hashCode() : 0); - result = 31 * result + (d != null ? d.hashCode() : 0); - result = 31 * result + (f != null ? f.hashCode() : 0); - result = 31 * result + (bd != null ? bd.hashCode() : 0); - result = 31 * result + (bi != null ? bi.hashCode() : 0); - result = 31 * result + type.hashCode(); - return result; + return 31 * type.hashCode() + + switch (type) { + case STRING -> string.hashCode(); + case BOOL -> bool.hashCode(); + case INTEGER -> i.hashCode(); + case LONG -> l.hashCode(); + case DOUBLE -> d.hashCode(); + case FLOAT -> f.hashCode(); + case BIG_DECIMAL -> bd.hashCode(); + case BIG_INTEGER -> bi.hashCode(); + case MAP -> map.hashCode(); + case ARRAY -> array.hashCode(); + default -> 0; + }; } } diff --git a/src/main/java/io/synadia/json/JsonValueSupplier.java b/src/main/java/io/synadia/json/JsonValueSupplier.java index 0018ea8..4b7ddfa 100644 --- a/src/main/java/io/synadia/json/JsonValueSupplier.java +++ b/src/main/java/io/synadia/json/JsonValueSupplier.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/JsonValueType.java b/src/main/java/io/synadia/json/JsonValueType.java index efdcf11..9961797 100644 --- a/src/main/java/io/synadia/json/JsonValueType.java +++ b/src/main/java/io/synadia/json/JsonValueType.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/JsonValueUtils.java b/src/main/java/io/synadia/json/JsonValueUtils.java index 8528186..6f3a7f2 100644 --- a/src/main/java/io/synadia/json/JsonValueUtils.java +++ b/src/main/java/io/synadia/json/JsonValueUtils.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/JsonWriteUtils.java b/src/main/java/io/synadia/json/JsonWriteUtils.java index eb09b7c..0f54dfd 100644 --- a/src/main/java/io/synadia/json/JsonWriteUtils.java +++ b/src/main/java/io/synadia/json/JsonWriteUtils.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/ListValueResolver.java b/src/main/java/io/synadia/json/ListValueResolver.java index 2fbdb76..688ee20 100644 --- a/src/main/java/io/synadia/json/ListValueResolver.java +++ b/src/main/java/io/synadia/json/ListValueResolver.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/main/java/io/synadia/json/MapBuilder.java b/src/main/java/io/synadia/json/MapBuilder.java index a33dfcd..8334aaa 100644 --- a/src/main/java/io/synadia/json/MapBuilder.java +++ b/src/main/java/io/synadia/json/MapBuilder.java @@ -1,4 +1,5 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/test/java/io/nats/client/api/AckPolicy.java b/src/test/java/io/nats/client/api/AckPolicy.java deleted file mode 100644 index edb092a..0000000 --- a/src/test/java/io/nats/client/api/AckPolicy.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Represents the Ack Policy of a consumer - */ -public enum AckPolicy { - /** - * Messages are acknowledged as soon as the server sends them. Clients do not need to ack. - */ - None("none"), - /** - * All messages with a sequence number less than the message acked are also acknowledged. E.g. reading a batch of messages 1 .. 100. Ack on message 100 will acknowledge 1 .. 99 as well. - */ - All("all"), - /** - * Each message must be acknowledged individually. Message can be acked out of sequence and create gaps of unacknowledged messages in the consumer. - */ - Explicit("explicit"); - - private final String policy; - - AckPolicy(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (AckPolicy env : AckPolicy.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static AckPolicy get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/ApiResponse.java b/src/test/java/io/nats/client/api/ApiResponse.java deleted file mode 100644 index ebde7a0..0000000 --- a/src/test/java/io/nats/client/api/ApiResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; - -public abstract class ApiResponse { - - protected final JsonValue jv; - - public ApiResponse(JsonValue jsonValue) { - jv = jsonValue; - } -} diff --git a/src/test/java/io/nats/client/api/ClusterInfo.java b/src/test/java/io/nats/client/api/ClusterInfo.java deleted file mode 100644 index b2f4114..0000000 --- a/src/test/java/io/nats/client/api/ClusterInfo.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.Nullable; - -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.readString; -import static io.synadia.json.JsonValueUtils.readValue; - -/** - * Information about the cluster a stream is part of. - */ -public class ClusterInfo { - private final String name; - private final String leader; - private final List replicas; - - static ClusterInfo optionalInstance(JsonValue v) { - return v == null ? null : new ClusterInfo(v); - } - - ClusterInfo(JsonValue v) { - name = readString(v, NAME); - leader = readString(v, LEADER); - replicas = Replica.optionalListOf(readValue(v, REPLICAS)); - } - - /** - * The cluster name. Technically can be null - * @return the cluster or null - */ - @Nullable - public String getName() { - return name; - } - - /** - * The server name of the RAFT leader - * @return the leader or null - */ - @Nullable - public String getLeader() { - return leader; - } - - /** - * The members of the RAFT cluster. May be null if there are no replicas. - * @return the replicas or null - */ - @Nullable - public List getReplicas() { - return replicas; - } - - @Override - public String toString() { - return "ClusterInfo{" + - "name='" + name + '\'' + - ", leader='" + leader + '\'' + - ", replicas=" + replicas + - '}'; - } -} diff --git a/src/test/java/io/nats/client/api/CompressionOption.java b/src/test/java/io/nats/client/api/CompressionOption.java deleted file mode 100644 index e4ff97c..0000000 --- a/src/test/java/io/nats/client/api/CompressionOption.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Stream compression policies. - */ -public enum CompressionOption { - None("none"), - S2("s2"); - - private final String policy; - - CompressionOption(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (CompressionOption env : CompressionOption.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static CompressionOption get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/ConsumerConfiguration.java b/src/test/java/io/nats/client/api/ConsumerConfiguration.java deleted file mode 100644 index b301328..0000000 --- a/src/test/java/io/nats/client/api/ConsumerConfiguration.java +++ /dev/null @@ -1,1424 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.nats.client.support.ApiConstants; -import io.synadia.json.JsonParseException; -import io.synadia.json.JsonParser; -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.*; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; -import static io.synadia.json.JsonWriteUtils.*; - -public class ConsumerConfiguration implements JsonSerializable { - @Deprecated - public static final Duration DURATION_MIN = Duration.ofNanos(1); - - public static final DeliverPolicy DEFAULT_DELIVER_POLICY = DeliverPolicy.All; - public static final AckPolicy DEFAULT_ACK_POLICY = AckPolicy.Explicit; - public static final ReplayPolicy DEFAULT_REPLAY_POLICY = ReplayPolicy.Instant; - public static final PriorityPolicy DEFAULT_PRIORITY_POLICY = PriorityPolicy.None; - - public static final Duration DURATION_UNSET = Duration.ZERO; - public static final Duration MIN_IDLE_HEARTBEAT = Duration.ofMillis(100); - - public static final int INTEGER_UNSET = -1; - public static final long LONG_UNSET = -1; - public static final long ULONG_UNSET = 0; - public static final long DURATION_UNSET_LONG = 0; - public static final long DURATION_MIN_LONG = 1; - public static final int STANDARD_MIN = 0; - public static final int MAX_DELIVER_MIN = 1; - - public static final long MIN_IDLE_HEARTBEAT_NANOS = MIN_IDLE_HEARTBEAT.toNanos(); - public static final long MIN_IDLE_HEARTBEAT_MILLIS = MIN_IDLE_HEARTBEAT.toMillis(); - - protected final DeliverPolicy deliverPolicy; - protected final AckPolicy ackPolicy; - protected final ReplayPolicy replayPolicy; - protected final String description; - protected final String durable; - protected final String name; - protected final String deliverSubject; - protected final String deliverGroup; - protected final String sampleFrequency; - protected final ZonedDateTime startTime; - protected final Duration ackWait; - protected final Duration idleHeartbeat; - protected final Duration maxExpires; - protected final Duration inactiveThreshold; - protected final Long startSeq; // server side this is unsigned - protected final Integer maxDeliver; - protected final Long rateLimit; // server side this is unsigned - protected final Integer maxAckPending; - protected final Integer maxPullWaiting; - protected final Integer maxBatch; - protected final Integer maxBytes; - protected final Integer numReplicas; - protected final ZonedDateTime pauseUntil; - protected final Boolean flowControl; - protected final Boolean headersOnly; - protected final Boolean memStorage; - protected final List backoff; - protected final Map metadata; - protected final List filterSubjects; - protected final List priorityGroups; - protected final PriorityPolicy priorityPolicy; - - // For the builder - protected ConsumerConfiguration(Builder b) - { - this.deliverPolicy = b.deliverPolicy; - this.ackPolicy = b.ackPolicy; - this.replayPolicy = b.replayPolicy; - - this.description = b.description; - this.durable = b.durable; - this.name = b.name; - this.startTime = b.startTime; - this.ackWait = b.ackWait; - this.sampleFrequency = b.sampleFrequency; - this.deliverSubject = b.deliverSubject; - this.deliverGroup = b.deliverGroup; - this.idleHeartbeat = b.idleHeartbeat; - this.maxExpires = b.maxExpires; - this.inactiveThreshold = b.inactiveThreshold; - - this.startSeq = b.startSeq; - this.maxDeliver = b.maxDeliver; - this.rateLimit = b.rateLimit; - this.maxAckPending = b.maxAckPending; - this.maxPullWaiting = b.maxPullWaiting; - this.maxBatch = b.maxBatch; - this.maxBytes = b.maxBytes; - this.numReplicas = b.numReplicas; - this.pauseUntil = b.pauseUntil; - - this.flowControl = b.flowControl; - this.headersOnly = b.headersOnly; - this.memStorage = b.memStorage; - - this.backoff = b.backoff; - this.metadata = b.metadata; - this.filterSubjects = b.filterSubjects; - - this.priorityGroups = b.priorityGroups; - this.priorityPolicy = b.priorityPolicy; - } - - /** - * Returns a JSON representation of this consumer configuration. - * @return json consumer configuration json string - */ - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, DESCRIPTION, description); - addField(sb, DURABLE_NAME, durable); - addField(sb, NAME, name); - addField(sb, DELIVER_SUBJECT, deliverSubject); - addField(sb, DELIVER_GROUP, deliverGroup); - addField(sb, DELIVER_POLICY, GetOrDefault(deliverPolicy).toString()); - addFieldWhenGtZero(sb, OPT_START_SEQ, startSeq); - addField(sb, OPT_START_TIME, startTime); - addField(sb, ACK_POLICY, GetOrDefault(ackPolicy).toString()); - addFieldAsNanos(sb, ACK_WAIT, ackWait); - addFieldWhenGtZero(sb, MAX_DELIVER, maxDeliver); - addField(sb, MAX_ACK_PENDING, maxAckPending); - addField(sb, REPLAY_POLICY, GetOrDefault(replayPolicy).toString()); - addField(sb, SAMPLE_FREQ, sampleFrequency); - addFieldWhenGtZero(sb, RATE_LIMIT_BPS, rateLimit); - addFieldAsNanos(sb, IDLE_HEARTBEAT, idleHeartbeat); - addField(sb, FLOW_CONTROL, flowControl); - addField(sb, ApiConstants.MAX_WAITING, maxPullWaiting); - addField(sb, HEADERS_ONLY, headersOnly); - addField(sb, MAX_BATCH, maxBatch); - addField(sb, MAX_BYTES, maxBytes); - addFieldAsNanos(sb, MAX_EXPIRES, maxExpires); - addFieldAsNanos(sb, INACTIVE_THRESHOLD, inactiveThreshold); - addDurations(sb, BACKOFF, backoff); - addField(sb, NUM_REPLICAS, numReplicas); - addField(sb, PAUSE_UNTIL, pauseUntil); - addField(sb, MEM_STORAGE, memStorage); - addField(sb, METADATA, metadata); - if (filterSubjects != null) { - if (filterSubjects.size() > 1) { - addStrings(sb, FILTER_SUBJECTS, filterSubjects); - } - else if (filterSubjects.size() == 1) { - addField(sb, FILTER_SUBJECT, filterSubjects.getFirst()); - } - } - addStrings(sb, PRIORITY_GROUPS, priorityGroups); - if (priorityPolicy != null && priorityPolicy != DEFAULT_PRIORITY_POLICY) { - addField(sb, PRIORITY_POLICY, priorityPolicy.toString()); - } - return endJson(sb).toString(); - } - - /** - * Gets the name of the description of this consumer configuration. - * @return name of the description. - */ - @Nullable - public String getDescription() { - return description; - } - - /** - * Gets the name of the durable name for this consumer configuration. - * @return name of the durable. - */ - @Nullable - public String getDurable() { - return durable; - } - - /** - * Gets the name of the consumer name for this consumer configuration. - * @return name of the consumer. - */ - @Nullable - public String getName() { - return name; - } - - /** - * Gets the deliver subject of this consumer configuration. - * @return the deliver subject. - */ - @Nullable - public String getDeliverSubject() { - return deliverSubject; - } - - /** - * Gets the deliver group of this consumer configuration. - * @return the deliver group. - */ - @Nullable - public String getDeliverGroup() { - return deliverGroup; - } - - /** - * Gets the deliver policy of this consumer configuration. - * @return the deliver policy. - */ - @NonNull - public DeliverPolicy getDeliverPolicy() { - return GetOrDefault(deliverPolicy); - } - - /** - * Gets the start sequence of this consumer configuration. - * @return the start sequence. - */ - public long getStartSequence() { - return getOrUnsetUlong(startSeq); - } - - /** - * Gets the start time of this consumer configuration. - * @return the start time. - */ - @Nullable - public ZonedDateTime getStartTime() { - return startTime; - } - - /** - * Gets the acknowledgment policy of this consumer configuration. - * @return the acknowledgment policy. - */ - @NonNull - public AckPolicy getAckPolicy() { - return GetOrDefault(ackPolicy); - } - - /** - * Gets the acknowledgment wait of this consumer configuration. - * @return the acknowledgment wait duration. - */ - @Nullable - public Duration getAckWait() { - return ackWait; - } - - /** - * Gets the max delivery amount of this consumer configuration. - * @return the max delivery amount. - */ - public long getMaxDeliver() { - return getOrUnset(maxDeliver); - } - - /** - * Gets the filter subject of this consumer configuration. - * With the introduction of multiple filter subjects, this method will - * return null if there are not exactly one filter subjects - * @return the first filter subject. - */ - @Nullable - public String getFilterSubject() { - return filterSubjects == null || filterSubjects.size() != 1 ? null : filterSubjects.get(0); - } - - /** - * Gets the filter subjects as a list. May be null, otherwise won't be empty - * @return the list - */ - @Nullable - public List getFilterSubjects() { - return filterSubjects; - } - - /** - * Gets the priority groups as a list. May be null, otherwise won't be empty - * @return the list - */ - @Nullable - public List getPriorityGroups() { - return priorityGroups; - } - - /** - * Whether there are multiple filter subjects for this consumer configuration. - * @return true if there are multiple filter subjects - */ - public boolean hasMultipleFilterSubjects() { - return filterSubjects != null && filterSubjects.size() > 1; - } - - /** - * Gets the replay policy of this consumer configuration. - * @return the replay policy. - */ - @NonNull - public ReplayPolicy getReplayPolicy() { - return GetOrDefault(replayPolicy); - } - - /** - * Gets the rate limit for this consumer configuration. - * @return the rate limit in bits per second - */ - public long getRateLimit() { - return getOrUnsetUlong(rateLimit); - } - - /** - * Gets the maximum ack pending configuration. - * @return maximum ack pending. - */ - public long getMaxAckPending() { - return getOrUnset(maxAckPending); - } - - /** - * Gets the sample frequency. - * @return sampleFrequency. - */ - @Nullable - public String getSampleFrequency() { - return sampleFrequency; - } - - /** - * Gets the idle heart beat wait time - * @return the idle heart beat wait duration. - */ - @Nullable - public Duration getIdleHeartbeat() { - return idleHeartbeat; - } - - /** - * Get the flow control flag indicating whether it's on or off - * @return the flow control mode - */ - public boolean isFlowControl() { - // The way the builder and json reading works it's never false if it's not null - // but this way I can make code coverage happy and not assume. - return Boolean.TRUE.equals(flowControl); - } - - /** - * Get the number of pulls that can be outstanding on a pull consumer - * @return the max pull waiting - */ - public long getMaxPullWaiting() { - return getOrUnset(maxPullWaiting); - } - - /** - * Get the header only flag indicating whether it's on or off - * @return the flow control mode - */ - public boolean isHeadersOnly() { - return headersOnly != null && headersOnly; - } - - /** - * Get the mem storage flag whether it's on or off. - * @return the mem storage mode - */ - public boolean isMemStorage() { - return memStorage != null && memStorage; - } - - /** - * Get the max batch size for the server to allow on pull requests. - * @return the max batch size - */ - public long getMaxBatch() { - return getOrUnset(maxBatch); - } - - /** - * Get the max bytes size for the server to allow on pull requests. - * @return the max byte size - */ - public long getMaxBytes() { - return getOrUnset(maxBytes); - } - - /** - * Get the max amount of expire time for the server to allow on pull requests. - * @return the max expire - */ - @Nullable - public Duration getMaxExpires() { - return maxExpires; - } - - /** - * Get the amount of time before the consumer is deemed inactive. - * @return the inactive threshold - */ - @Nullable - public Duration getInactiveThreshold() { - return inactiveThreshold; - } - - /** - * Get the backoff list; may be empty, will never be null. - * @return the list - */ - @NonNull - public List getBackoff() { - return backoff == null ? Collections.emptyList() : backoff; - } - - /** - * Metadata for the consumer; may be empty, will never be null. - * @return the metadata map - */ - @NonNull - public Map getMetadata() { - return metadata == null ? Collections.emptyMap() : metadata; - } - - /** - * Get the number of consumer replicas. - * @return the replicas count - */ - public int getNumReplicas() { return getOrUnset(numReplicas); } - - /** - * Get the time until the consumer is paused. - * @return paused until time - */ - @Nullable - public ZonedDateTime getPauseUntil() { - return pauseUntil; - } - - /** - * Gets the priority policy of this consumer configuration. - * @return the priority policy. - */ - @NonNull - public PriorityPolicy getPriorityPolicy() { - return GetOrDefault(priorityPolicy); - } - - /** - * Gets whether deliver policy of this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean deliverPolicyWasSet() { - return deliverPolicy != null; - } - - /** - * Gets whether ack policy for this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean ackPolicyWasSet() { - return ackPolicy != null; - } - - /** - * Gets whether replay policy for this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean replayPolicyWasSet() { - return replayPolicy != null; - } - - /** - * Gets whether start sequence for this consumer configuration was set or left unset - * @return true if the start sequence was set by the user - */ - public boolean startSeqWasSet() { - return startSeq != null; - } - - /** - * Gets whether max deliver for this consumer configuration was set or left unset - * @return true if max deliver was set by the user - */ - public boolean maxDeliverWasSet() { - return maxDeliver != null; - } - - /** - * Gets whether rate limit for this consumer configuration was set or left unset - * @return true if rate limit was set by the user - */ - public boolean rateLimitWasSet() { - return rateLimit != null; - } - - /** - * Gets whether max ack pending for this consumer configuration was set or left unset - * @return true if mac ack pending was set by the user - */ - public boolean maxAckPendingWasSet() { - return maxAckPending != null; - } - - /** - * Gets whether max pull waiting for this consumer configuration was set or left unset - * @return true if max pull waiting was set by the user - */ - public boolean maxPullWaitingWasSet() { - return maxPullWaiting != null; - } - - /** - * Gets whether max batch for this consumer configuration was set or left unset - * @return true if max batch was set by the user - */ - public boolean maxBatchWasSet() { - return maxBatch != null; - } - - /** - * Gets whether max bytes for this consumer configuration was set or left unset - * @return true if max bytes was set by the user - */ - public boolean maxBytesWasSet() { - return maxBytes != null; - } - - /** - * Gets whether flow control for this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean flowControlWasSet() { - return flowControl != null; - } - - /** - * Gets whether headers only for this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean headersOnlyWasSet() { - return headersOnly != null; - } - - /** - * Gets whether mem storage for this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean memStorageWasSet() { - return memStorage != null; - } - - /** - * Gets whether num replicas for this consumer configuration was set or left unset - * @return true if num replicas was set by the user - */ - public boolean numReplicasWasSet() { - return numReplicas != null; - } - - /** - * Gets whether backoff for this consumer configuration was set or left unset - * @return true if num backoff was set by the user - */ - public boolean backoffWasSet() { - return backoff != null; - } - - /** - * Gets whether metadata for this consumer configuration was set or left unset - * @return true if num metadata was set by the user - */ - public boolean metadataWasSet() { - return metadata != null; - } - - /** - * Gets whether priority policy for this consumer configuration was set or left unset - * @return true if the policy was set, false if the policy was not set - */ - public boolean priorityPolicyWasSet() { - return priorityPolicy != null; - } - - /** - * Creates a builder for the options. - * @return a publish options builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Creates a builder for the options. - * @param cc the consumer configuration - * @return a publish options builder - */ - public static Builder builder(ConsumerConfiguration cc) { - return cc == null ? new Builder() : new Builder(cc); - } - - /** - * ConsumerConfiguration is created using a Builder. The builder supports chaining and will - * create a default set of options if no methods are calls. - * - *

{@code new ConsumerConfiguration.Builder().build()} will create a default ConsumerConfiguration. - * - */ - public static class Builder { - private DeliverPolicy deliverPolicy; - private AckPolicy ackPolicy; - private ReplayPolicy replayPolicy; - - private String description; - private String durable; - private String name; - private String deliverSubject; - private String deliverGroup; - private String sampleFrequency; - - private ZonedDateTime startTime; - private Duration ackWait; - private Duration idleHeartbeat; - private Duration maxExpires; - private Duration inactiveThreshold; - - private Long startSeq; - private Integer maxDeliver; - private Long rateLimit; - private Integer maxAckPending; - private Integer maxPullWaiting; - private Integer maxBatch; - private Integer maxBytes; - private Integer numReplicas; - private ZonedDateTime pauseUntil; - - private Boolean flowControl; - private Boolean headersOnly; - private Boolean memStorage; - - private List backoff; - private Map metadata; - private List filterSubjects; - - private List priorityGroups; - private PriorityPolicy priorityPolicy; - - /** - * Construct the builder - */ - public Builder() {} - - /** - * Construct the builder and initialize values with the existing ConsumerConfiguration - * @param cc the consumer configuration to clone - */ - public Builder(ConsumerConfiguration cc) { - if (cc != null) { - this.deliverPolicy = cc.deliverPolicy; - this.ackPolicy = cc.ackPolicy; - this.replayPolicy = cc.replayPolicy; - - this.description = cc.description; - this.durable = cc.durable; - this.name = cc.name; - this.deliverSubject = cc.deliverSubject; - this.deliverGroup = cc.deliverGroup; - this.sampleFrequency = cc.sampleFrequency; - - this.startTime = cc.startTime; - this.ackWait = cc.ackWait; - this.idleHeartbeat = cc.idleHeartbeat; - this.maxExpires = cc.maxExpires; - this.inactiveThreshold = cc.inactiveThreshold; - - this.startSeq = cc.startSeq; - this.maxDeliver = cc.maxDeliver; - this.rateLimit = cc.rateLimit; - this.maxAckPending = cc.maxAckPending; - this.maxPullWaiting = cc.maxPullWaiting; - this.maxBatch = cc.maxBatch; - this.maxBytes = cc.maxBytes; - this.numReplicas = cc.numReplicas; - this.pauseUntil = cc.pauseUntil; - - this.flowControl = cc.flowControl; - this.headersOnly = cc.headersOnly; - this.memStorage = cc.memStorage; - - if (cc.backoff != null) { - this.backoff = new ArrayList<>(cc.backoff); - } - if (cc.metadata != null) { - this.metadata = new HashMap<>(cc.metadata); - } - if (cc.filterSubjects != null) { - this.filterSubjects = new ArrayList<>(cc.filterSubjects); - } - - if (cc.priorityGroups != null) { - this.priorityGroups = new ArrayList<>(cc.priorityGroups); - } - this.priorityPolicy = cc.priorityPolicy; - } - } - - /** - * Initialize values from the json string. - * @param json the json string to parse - * @return the builder - * @throws JsonParseException if the json is invalid - */ - public Builder json(String json) throws JsonParseException { - return jsonValue(JsonParser.parse(json)); - } - - /** - * Initialize values from the JsonValue object. - * @param jsonValue the json value object - * @return the builder - */ - public Builder jsonValue(JsonValue jsonValue) { - deliverPolicy(DeliverPolicy.get(readString(jsonValue, DELIVER_POLICY))); - ackPolicy(AckPolicy.get(readString(jsonValue, ACK_POLICY))); - - replayPolicy(ReplayPolicy.get(readString(jsonValue, REPLAY_POLICY))); - - description(readString(jsonValue, DESCRIPTION)); - durable(readString(jsonValue, DURABLE_NAME)); - name(readString(jsonValue, NAME)); - deliverSubject(readString(jsonValue, DELIVER_SUBJECT)); - deliverGroup(readString(jsonValue, DELIVER_GROUP)); - sampleFrequency(readString(jsonValue, SAMPLE_FREQ)); - startTime(readDate(jsonValue, OPT_START_TIME)); - ackWait(readNanosAsDuration(jsonValue, ACK_WAIT)); - maxExpires(readNanosAsDuration(jsonValue, MAX_EXPIRES)); - inactiveThreshold(readNanosAsDuration(jsonValue, INACTIVE_THRESHOLD)); - - startSequence(readLong(jsonValue, OPT_START_SEQ)); - maxDeliver(readLong(jsonValue, MAX_DELIVER, INTEGER_UNSET)); - rateLimit(readLong(jsonValue, RATE_LIMIT_BPS)); - maxAckPending(readLong(jsonValue, MAX_ACK_PENDING)); - maxPullWaiting(readLong(jsonValue, MAX_WAITING)); - maxBatch(readLong(jsonValue, MAX_BATCH)); - maxBytes(readLong(jsonValue, MAX_BYTES)); - - Integer r = readInteger(jsonValue, NUM_REPLICAS); - if (r != null) { - if (r == 0) { - numReplicas = 0; - } - else { - numReplicas(r); - } - } - - pauseUntil(readDate(jsonValue, PAUSE_UNTIL)); - - Duration idleHeartbeat = readNanosAsDuration(jsonValue, IDLE_HEARTBEAT); - if (idleHeartbeat != null) { - if (readBoolean(jsonValue, FLOW_CONTROL, false)) { - flowControl(idleHeartbeat); - } - else { - idleHeartbeat(idleHeartbeat); - } - } - - headersOnly(readBoolean(jsonValue, HEADERS_ONLY)); - memStorage(readBoolean(jsonValue, MEM_STORAGE)); - - backoff(readNanosAsDurationListOrEmpty(jsonValue, BACKOFF).toArray(new Duration[0])); - - metadata(readStringMapOrNull(jsonValue, METADATA)); - - String fs = readString(jsonValue, FILTER_SUBJECT); - if (fs == null || fs.trim().isEmpty()) { - filterSubjects(readStringListOrNull(jsonValue, FILTER_SUBJECTS)); - } - else { - filterSubject(fs); - } - - priorityGroups(readStringListOrNull(jsonValue, PRIORITY_GROUPS)); - priorityPolicy(PriorityPolicy.get(readString(jsonValue, PRIORITY_POLICY))); - - return this; - } - - /** - * Sets the description - * @param description the description - * @return the builder - */ - public Builder description(String description) { - this.description = description == null || description.trim().isEmpty() ? null : description; - return this; - } - - /** - * Sets the name of the durable consumer. - * Null or empty clears the field. - * @param durable name of the durable consumer. - * @return the builder - */ - public Builder durable(String durable) { - this.durable = durable; - return this; - } - - /** - * Sets the name of the consumer. - * Null or empty clears the field. - * @param name name of the consumer. - * @return the builder - */ - public Builder name(String name) { - this.name = name; - return this; - } - - /** - * Sets the delivery policy of the ConsumerConfiguration. - * @param policy the delivery policy. - * @return Builder - */ - public Builder deliverPolicy(DeliverPolicy policy) { - this.deliverPolicy = policy; - return this; - } - - /** - * Sets the subject to deliver messages to. - *

By setting the deliverySubject this configuration will create a push consumer. When left empty or set to NULL a pull consumer will be created. - * @param subject the subject. - * @return the builder - */ - public Builder deliverSubject(String subject) { - this.deliverSubject = subject == null || subject.trim().isEmpty() ? null : subject; - return this; - } - - /** - * Sets the group to deliver messages to. - * @param group the delivery group. - * @return the builder - */ - public Builder deliverGroup(String group) { - this.deliverSubject = group == null || group.trim().isEmpty() ? null : group; - return this; - } - - /** - * Sets the start sequence of the ConsumerConfiguration or null to unset / clear. - * @param sequence the start sequence - * @return Builder - */ - public Builder startSequence(Long sequence) { - this.startSeq = normalizeUlong(sequence); - return this; - } - - /** - * Sets the start sequence of the ConsumerConfiguration. - * @param sequence the start sequence - * @return Builder - */ - public Builder startSequence(long sequence) { - this.startSeq = normalizeUlong(sequence); - return this; - } - - /** - * Sets the start time of the ConsumerConfiguration. - * @param startTime the start time - * @return Builder - */ - public Builder startTime(ZonedDateTime startTime) { - this.startTime = startTime; - return this; - } - - /** - * Sets the acknowledgement policy of the ConsumerConfiguration. - * @param policy the acknowledgement policy. - * @return Builder - */ - public Builder ackPolicy(AckPolicy policy) { - this.ackPolicy = policy; - return this; - } - - /** - * Sets the acknowledgement wait duration of the ConsumerConfiguration. - * @param timeout the wait timeout - * @return Builder - */ - public Builder ackWait(Duration timeout) { - this.ackWait = normalize(timeout); - return this; - } - - /** - * Sets the acknowledgement wait duration of the ConsumerConfiguration. - * @param timeoutMillis the wait timeout in milliseconds - * @return Builder - */ - public Builder ackWait(long timeoutMillis) { - this.ackWait = normalizeDuration(timeoutMillis); - return this; - } - - /** - * Sets the maximum delivery amount of the ConsumerConfiguration or null to unset / clear. - * @param maxDeliver the maximum delivery amount - * @return Builder - */ - public Builder maxDeliver(Long maxDeliver) { - this.maxDeliver = normalize(maxDeliver, MAX_DELIVER_MIN); - return this; - } - - /** - * Sets the maximum delivery amount of the ConsumerConfiguration. - * @param maxDeliver the maximum delivery amount - * @return Builder - */ - public Builder maxDeliver(long maxDeliver) { - this.maxDeliver = normalize(maxDeliver, MAX_DELIVER_MIN); - return this; - } - - /** - * Sets the filter subject of the ConsumerConfiguration. - * Replaces any other filter subjects set in the builder - * @param filterSubject the filter subject - * @return Builder - */ - public Builder filterSubject(String filterSubject) { - if (filterSubject == null || filterSubject.trim().isEmpty()) { - this.filterSubjects = null; - } - else { - this.filterSubjects = Collections.singletonList(filterSubject); - } - return this; - } - - /** - * Sets the filter subjects of the ConsumerConfiguration. - * Replaces any other filter subjects set in the builder - * @param filterSubjects one or more filter subjects - * @return Builder - */ - public Builder filterSubjects(String... filterSubjects) { - return filterSubjects(Arrays.asList(filterSubjects)); - } - - /** - * Sets the filter subjects of the ConsumerConfiguration. - * Replaces any other filter subjects set in the builder - * @param filterSubjects the list of filter subjects - * @return Builder - */ - public Builder filterSubjects(List filterSubjects) { - this.filterSubjects = new ArrayList<>(); - if (filterSubjects != null) { - for (String fs : filterSubjects) { - if (fs != null && !fs.trim().isEmpty()) { - this.filterSubjects.add(fs); - } - } - } - if (this.filterSubjects.isEmpty()) { - this.filterSubjects = null; - } - return this; - } - - /** - * Sets the replay policy of the ConsumerConfiguration. - * @param policy the replay policy. - * @return Builder - */ - public Builder replayPolicy(ReplayPolicy policy) { - this.replayPolicy = policy; - return this; - } - - /** - * Sets the sample frequency of the ConsumerConfiguration. - * @param frequency the frequency - * @return Builder - */ - public Builder sampleFrequency(String frequency) { - this.sampleFrequency = frequency == null || frequency.trim().isEmpty() ? null : frequency; - return this; - } - - /** - * Set the rate limit of the ConsumerConfiguration or null to unset / clear. - * @param bitsPerSecond bits per second to deliver - * @return Builder - */ - public Builder rateLimit(Long bitsPerSecond) { - this.rateLimit = normalizeUlong(bitsPerSecond); - return this; - } - - /** - * Set the rate limit of the ConsumerConfiguration. - * @param bitsPerSecond bits per second to deliver - * @return Builder - */ - public Builder rateLimit(long bitsPerSecond) { - this.rateLimit = normalizeUlong(bitsPerSecond); - return this; - } - - /** - * Sets the maximum ack pending or null to unset / clear. - * @param maxAckPending maximum pending acknowledgements. - * @return Builder - */ - public Builder maxAckPending(Long maxAckPending) { - this.maxAckPending = normalize(maxAckPending, STANDARD_MIN); - return this; - } - - /** - * Sets the maximum ack pending. - * @param maxAckPending maximum pending acknowledgements. - * @return Builder - */ - public Builder maxAckPending(long maxAckPending) { - this.maxAckPending = normalize(maxAckPending, STANDARD_MIN); - return this; - } - - /** - * sets the idle heart beat wait time - * @param idleHeartbeat the idle heart beat duration - * @return Builder - */ - public Builder idleHeartbeat(Duration idleHeartbeat) { - if (idleHeartbeat == null) { - this.idleHeartbeat = null; - } - else { - long nanos = idleHeartbeat.toNanos(); - if (nanos <= DURATION_UNSET_LONG) { - this.idleHeartbeat = DURATION_UNSET; - } - else if (nanos < MIN_IDLE_HEARTBEAT_NANOS) { - throw new IllegalArgumentException("Duration must be greater than or equal to " + MIN_IDLE_HEARTBEAT_NANOS + " nanos."); - } - else { - this.idleHeartbeat = idleHeartbeat; - } - } - return this; - } - - /** - * sets the idle heart beat wait time - * @param idleHeartbeatMillis the idle heart beat duration in milliseconds - * @return Builder - */ - public Builder idleHeartbeat(long idleHeartbeatMillis) { - if (idleHeartbeatMillis <= DURATION_UNSET_LONG) { - this.idleHeartbeat = DURATION_UNSET; - } - else if (idleHeartbeatMillis < MIN_IDLE_HEARTBEAT_MILLIS) { - throw new IllegalArgumentException("Duration must be greater than or equal to " + MIN_IDLE_HEARTBEAT_MILLIS + " milliseconds."); - } - else { - this.idleHeartbeat = Duration.ofMillis(idleHeartbeatMillis); - } - return this; - } - - /** - * set the flow control on and set the idle heartbeat - * @param idleHeartbeat the idle heart beat duration - * @return Builder - */ - public Builder flowControl(Duration idleHeartbeat) { - this.flowControl = true; - return idleHeartbeat(idleHeartbeat); - } - - /** - * set the flow control on and set the idle heartbeat - * @param idleHeartbeatMillis the idle heart beat duration in milliseconds - * @return Builder - */ - public Builder flowControl(long idleHeartbeatMillis) { - this.flowControl = true; - return idleHeartbeat(idleHeartbeatMillis); - } - - /** - * sets the max amount of expire time for the server to allow on pull requests. - * @param maxExpires the max expire duration - * @return Builder - */ - public Builder maxExpires(Duration maxExpires) { - this.maxExpires = normalize(maxExpires); - return this; - } - - /** - * sets the max amount of expire time for the server to allow on pull requests. - * @param maxExpires the max expire duration in milliseconds - * @return Builder - */ - public Builder maxExpires(long maxExpires) { - this.maxExpires = normalizeDuration(maxExpires); - return this; - } - - /** - * sets the amount of time before the consumer is deemed inactive. - * @param inactiveThreshold the threshold duration - * @return Builder - */ - public Builder inactiveThreshold(Duration inactiveThreshold) { - this.inactiveThreshold = normalize(inactiveThreshold); - return this; - } - - /** - * sets the amount of time before the consumer is deemed inactive. - * @param inactiveThreshold the threshold duration in milliseconds - * @return Builder - */ - public Builder inactiveThreshold(long inactiveThreshold) { - this.inactiveThreshold = normalizeDuration(inactiveThreshold); - return this; - } - - /** - * sets the max pull waiting, the number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored. - * Use null to unset / clear. - * @param maxPullWaiting the max pull waiting - * @return Builder - */ - public Builder maxPullWaiting(Long maxPullWaiting) { - this.maxPullWaiting = normalize(maxPullWaiting, STANDARD_MIN); - return this; - } - - /** - * sets the max pull waiting, the number of pulls that can be outstanding on a pull consumer, pulls received after this is reached are ignored. - * @param maxPullWaiting the max pull waiting - * @return Builder - */ - public Builder maxPullWaiting(long maxPullWaiting) { - this.maxPullWaiting = normalize(maxPullWaiting, STANDARD_MIN); - return this; - } - - /** - * sets the max batch size for the server to allow on pull requests. - * @param maxBatch the max batch size - * @return Builder - */ - public Builder maxBatch(Long maxBatch) { - this.maxBatch = normalize(maxBatch, STANDARD_MIN); - return this; - } - - /** - * sets the max batch size for the server to allow on pull requests. - * @param maxBatch the max batch size - * @return Builder - */ - public Builder maxBatch(long maxBatch) { - this.maxBatch = normalize(maxBatch, STANDARD_MIN); - return this; - } - - /** - * sets the max bytes size for the server to allow on pull requests. - * @param maxBytes the max bytes size - * @return Builder - */ - public Builder maxBytes(Long maxBytes) { - this.maxBytes = normalize(maxBytes, STANDARD_MIN); - return this; - } - - /** - * sets the max bytes size for the server to allow on pull requests. - * @param maxBytes the max bytes size - * @return Builder - */ - public Builder maxBytes(long maxBytes) { - this.maxBytes = normalize(maxBytes, STANDARD_MIN); - return this; - } - - /** - * set the number of replicas for the consumer. When set do not inherit the - * replica count from the stream but specifically set it to this amount. - * @param numReplicas number of replicas for the consumer - * @return Builder - */ - public Builder numReplicas(Integer numReplicas) { - this.numReplicas = numReplicas; - return this; - } - - /** - * Sets the time to pause the consumer until. - * @param pauseUntil the time to pause - * @return Builder - */ - public Builder pauseUntil(ZonedDateTime pauseUntil) { - this.pauseUntil = pauseUntil; - return this; - } - - /** - * set the headers only flag saying to deliver only the headers of - * messages in the stream and not the bodies - * @param headersOnly the flag - * @return Builder - */ - public Builder headersOnly(Boolean headersOnly) { - this.headersOnly = headersOnly; - return this; - } - - /** - * set the mem storage flag to force the consumer state to be kept - * in memory rather than inherit the setting from the stream - * @param memStorage the flag - * @return Builder - */ - public Builder memStorage(Boolean memStorage) { - this.memStorage = memStorage; - return this; - } - - /** - * Set the list of backoff. Will override ackwait setting. - * @see Delivery Reliability - * @param backoffs zero or more backoff durations or an array of backoffs - * @return Builder - */ - public Builder backoff(Duration... backoffs) { - if (backoffs == null || (backoffs.length == 1 && backoffs[0] == null)) - { - backoff = null; - } - else - { - backoff = new ArrayList<>(); - for (Duration d : backoffs) - { - if (d != null) - { - if (d.toNanos() < DURATION_MIN_LONG) - { - throw new IllegalArgumentException("Backoff cannot be less than " + DURATION_MIN_LONG); - } - backoff.add(d); - } - } - } - return this; - } - - /** - * Set the list of backoff. Will override ackwait setting. - * @see Delivery Reliability - * @param backoffsMillis zero or more backoff in millis or an array of backoffsMillis - * @return Builder - */ - public Builder backoff(long... backoffsMillis) { - if (backoffsMillis == null) { - backoff = null; - } - else { - backoff = new ArrayList<>(); - for (long ms : backoffsMillis) { - if (ms < DURATION_MIN_LONG) { - throw new IllegalArgumentException("Backoff cannot be less than " + DURATION_MIN_LONG); - } - this.backoff.add(Duration.ofMillis(ms)); - } - } - return this; - } - - /** - * Sets the metadata for the configuration - * @param metadata the metadata map - * @return Builder - */ - public Builder metadata(Map metadata) { - this.metadata = metadata == null || metadata.isEmpty() ? null : new HashMap<>(metadata); - return this; - } - - /** - * Sets the priority groups of the ConsumerConfiguration. - * Replaces any other priority groups set in the builder - * @param priorityGroups one or more priority groups - * @return Builder - */ - public Builder priorityGroups(String... priorityGroups) { - return priorityGroups(Arrays.asList(priorityGroups)); - } - - /** - * Sets the priority groups of the ConsumerConfiguration. - * Replaces any other priority groups set in the builder - * @param priorityGroups the list of priority groups - * @return Builder - */ - public Builder priorityGroups(List priorityGroups) { - this.priorityGroups = new ArrayList<>(); - if (priorityGroups != null) { - for (String pg : priorityGroups) { - if (pg != null && !pg.trim().isEmpty()) { - this.priorityGroups.add(pg); - } - } - } - if (this.priorityGroups.isEmpty()) { - this.priorityGroups = null; - } - return this; - } - - /** - * Sets the priority policy of the ConsumerConfiguration. - * @param policy the priority policy. - * @return Builder - */ - public Builder priorityPolicy(PriorityPolicy policy) { - this.priorityPolicy = policy; - return this; - } - - /** - * Builds the ConsumerConfiguration - * @return The consumer configuration. - */ - public ConsumerConfiguration build() { - return new ConsumerConfiguration(this); - } - } - - @Override - public String toString() { - return "ConsumerConfiguration " + toJson(); - } - - protected static int getOrUnset(Integer val) - { - return val == null ? INTEGER_UNSET : val; - } - - protected static long getOrUnsetUlong(Long val) - { - return val == null || val < 0 ? ULONG_UNSET : val; - } - - protected static Integer normalize(Long l, int min) { - if (l == null) { - return null; - } - - if (l < min) { - return INTEGER_UNSET; - } - - if (l > Integer.MAX_VALUE) { - return Integer.MAX_VALUE; - } - - return l.intValue(); - } - - protected static Long normalizeUlong(Long u) - { - return u == null ? null : u <= ULONG_UNSET ? ULONG_UNSET : u; - } - - protected static Duration normalize(Duration d) - { - return d == null ? null : d.toNanos() <= DURATION_UNSET_LONG ? DURATION_UNSET : d; - } - - protected static Duration normalizeDuration(long millis) - { - return millis <= DURATION_UNSET_LONG ? DURATION_UNSET : Duration.ofMillis(millis); - } - - protected static DeliverPolicy GetOrDefault(DeliverPolicy p) { return p == null ? DEFAULT_DELIVER_POLICY : p; } - protected static AckPolicy GetOrDefault(AckPolicy p) { return p == null ? DEFAULT_ACK_POLICY : p; } - protected static ReplayPolicy GetOrDefault(ReplayPolicy p) { return p == null ? DEFAULT_REPLAY_POLICY : p; } - protected static PriorityPolicy GetOrDefault(PriorityPolicy p) { return p == null ? DEFAULT_PRIORITY_POLICY : p; } -} diff --git a/src/test/java/io/nats/client/api/ConsumerLimits.java b/src/test/java/io/nats/client/api/ConsumerLimits.java deleted file mode 100644 index afab22c..0000000 --- a/src/test/java/io/nats/client/api/ConsumerLimits.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.time.Duration; - -import static io.nats.client.api.ConsumerConfiguration.getOrUnset; -import static io.nats.client.support.ApiConstants.INACTIVE_THRESHOLD; -import static io.nats.client.support.ApiConstants.MAX_ACK_PENDING; -import static io.synadia.json.JsonValueUtils.readInteger; -import static io.synadia.json.JsonValueUtils.readNanosAsDuration; -import static io.synadia.json.JsonWriteUtils.*; - -/** - * ConsumerLimits - */ -public class ConsumerLimits implements JsonSerializable { - private final Duration inactiveThreshold; - private final Integer maxAckPending; - - static ConsumerLimits optionalInstance(JsonValue vConsumerLimits) { - return vConsumerLimits == null ? null : new ConsumerLimits(vConsumerLimits); - } - - public ConsumerLimits(JsonValue vConsumerLimits) { - inactiveThreshold = readNanosAsDuration(vConsumerLimits, INACTIVE_THRESHOLD); - maxAckPending = readInteger(vConsumerLimits, MAX_ACK_PENDING); - } - - /** - * Maximum value for inactive_threshold for consumers of this stream. Acts as a default when consumers do not set this value. - * @return the inactive threshold limit - */ - @Nullable - public Duration getInactiveThreshold() { - return inactiveThreshold; - } - - /** - * Maximum value for max_ack_pending for consumers of this stream. Acts as a default when consumers do not set this value. - * @return maximum ack pending limit - */ - public long getMaxAckPending() { - return getOrUnset(maxAckPending); - } - - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addFieldAsNanos(sb, INACTIVE_THRESHOLD, inactiveThreshold); - addField(sb, MAX_ACK_PENDING, maxAckPending); - return endJson(sb).toString(); - } -} diff --git a/src/test/java/io/nats/client/api/DeliverPolicy.java b/src/test/java/io/nats/client/api/DeliverPolicy.java deleted file mode 100644 index ca616c3..0000000 --- a/src/test/java/io/nats/client/api/DeliverPolicy.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * The delivery policy for this consumer. - */ -public enum DeliverPolicy { - All("all"), - Last("last"), - New("new"), - ByStartSequence("by_start_sequence"), - ByStartTime("by_start_time"), - LastPerSubject("last_per_subject"); - - private final String policy; - - DeliverPolicy(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (DeliverPolicy env : DeliverPolicy.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static DeliverPolicy get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/DiscardPolicy.java b/src/test/java/io/nats/client/api/DiscardPolicy.java deleted file mode 100644 index bc27b22..0000000 --- a/src/test/java/io/nats/client/api/DiscardPolicy.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Stream discard policies - */ -public enum DiscardPolicy { - New("new"), - Old("old"); - - private final String policy; - - DiscardPolicy(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (DiscardPolicy env : DiscardPolicy.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static DiscardPolicy get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/External.java b/src/test/java/io/nats/client/api/External.java deleted file mode 100644 index fe5705c..0000000 --- a/src/test/java/io/nats/client/api/External.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import io.synadia.json.JsonValueUtils; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import static io.nats.client.support.ApiConstants.API; -import static io.nats.client.support.ApiConstants.DELIVER; -import static io.synadia.json.JsonWriteUtils.*; - -/** - * External configuration referencing a stream source in another account - */ -public class External implements JsonSerializable { - private final String api; - private final String deliver; - - static External optionalInstance(JsonValue vExternal) { - return vExternal == null ? null : new External(vExternal); - } - - External(JsonValue vExternal) { - api = JsonValueUtils.readString(vExternal, API); - deliver = JsonValueUtils.readString(vExternal, DELIVER); - } - - /** - * Construct the External configuration - * @param api the api prefix - * @param deliver the delivery subject - */ - public External(String api, String deliver) { - this.api = api; - this.deliver = deliver; - } - - /** - * Returns a JSON representation of this mirror - * - * @return json mirror json string - */ - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, API, api); - addField(sb, DELIVER, deliver); - return endJson(sb).toString(); - } - - /** - * The subject prefix that imports the other account $JS.API.CONSUMER.> subjects - * @return the api prefix - */ - @Nullable - public String getApi() { - return api; - } - - /** - * The delivery subject to use for the push consumer. - * @return delivery subject - */ - @Nullable - public String getDeliver() { - return deliver; - } - - @Override - public String toString() { - return "External{" + - "api='" + api + '\'' + - ", deliver='" + deliver + '\'' + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - External external = (External) o; - - if (api != null ? !api.equals(external.api) : external.api != null) return false; - return deliver != null ? deliver.equals(external.deliver) : external.deliver == null; - } - - @Override - public int hashCode() { - int result = api != null ? api.hashCode() : 0; - result = 31 * result + (deliver != null ? deliver.hashCode() : 0); - return result; - } - - /** - * Creates a builder for an External object. - * @return the builder. - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Placement can be created using a Builder. - */ - public static class Builder { - private String api; - private String deliver; - - /** - * Set the api string. - * @param api the api - * @return the builder - */ - public Builder api(String api) { - this.api = api; - return this; - } - - /** - * Set the deliver string. - * @param deliver the deliver - * @return the builder - */ - public Builder deliver(String deliver) { - this.deliver = deliver; - return this; - } - - /** - * Build an External object - * @return the External object - */ - public External build() { - return new External(api, deliver); - } - } -} diff --git a/src/test/java/io/nats/client/api/LostStreamData.java b/src/test/java/io/nats/client/api/LostStreamData.java deleted file mode 100644 index f2832db..0000000 --- a/src/test/java/io/nats/client/api/LostStreamData.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.util.List; - -import static io.nats.client.support.ApiConstants.BYTES; -import static io.nats.client.support.ApiConstants.MSGS; -import static io.synadia.json.JsonValueUtils.readLong; -import static io.synadia.json.JsonValueUtils.readLongListOrEmpty; - -/** - * Information about lost stream data - */ -public class LostStreamData { - private final List messages; - private final Long bytes; - - static LostStreamData optionalInstance(JsonValue vLost) { - return vLost == null ? null : new LostStreamData(vLost); - } - - - LostStreamData(JsonValue vLost) { - messages = readLongListOrEmpty(vLost, MSGS); - bytes = readLong(vLost, BYTES); - } - - /** - * Get the lost message ids. May be empty - * @return the list of message ids - */ - @NonNull - public List getMessages() { - return messages; - } - - /** - * Get the number of bytes that were lost - * @return the number of lost bytes - */ - @Nullable - public Long getBytes() { - return bytes; - } - - @Override - public String toString() { - return "LostStreamData{" + - "messages=" + messages + - ", bytes=" + bytes + - '}'; - } -} diff --git a/src/test/java/io/nats/client/api/Mirror.java b/src/test/java/io/nats/client/api/Mirror.java deleted file mode 100644 index a3a0ea9..0000000 --- a/src/test/java/io/nats/client/api/Mirror.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; - -/** - * Mirror Information. Maintains a 1:1 mirror of another stream with name matching this property. - * When a mirror is configured subjects and sources must be empty. - */ -public class Mirror extends SourceBase { - - public static Mirror optionalInstance(JsonValue vMirror) { - return vMirror == null ? null : new Mirror(vMirror); - } - - public Mirror(JsonValue vMirror) { - super(vMirror); - } -} diff --git a/src/test/java/io/nats/client/api/MirrorInfo.java b/src/test/java/io/nats/client/api/MirrorInfo.java deleted file mode 100644 index a4402e5..0000000 --- a/src/test/java/io/nats/client/api/MirrorInfo.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - - -import io.synadia.json.JsonValue; - -/** - * Information about an upstream stream source in a mirror - */ -public class MirrorInfo extends SourceInfoBase { - - static MirrorInfo optionalInstance(JsonValue vMirror) { - return vMirror == null ? null : new MirrorInfo(vMirror); - } - - MirrorInfo(JsonValue vMirror) { - super(vMirror); - } - - @Override - public String toString() { - return "MirrorInfo " + super.toString(); - } -} diff --git a/src/test/java/io/nats/client/api/PeerInfo.java b/src/test/java/io/nats/client/api/PeerInfo.java deleted file mode 100644 index 147962a..0000000 --- a/src/test/java/io/nats/client/api/PeerInfo.java +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; - -import java.time.Duration; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; - -public class PeerInfo { - - private final String name; - private final boolean current; - private final boolean offline; - private final Duration active; - private final long lag; - - PeerInfo(JsonValue vPeerInfo) { - name = readString(vPeerInfo, NAME); - current = readBoolean(vPeerInfo, CURRENT, false); - offline = readBoolean(vPeerInfo, OFFLINE, false); - active = readNanosAsDuration(vPeerInfo, ACTIVE, Duration.ZERO); - lag = readLong(vPeerInfo, LAG, 0); - } - - /** - * The server name of the peer - * @return the name - */ - @NonNull - public String getName() { - return name; - } - - /** - * Indicates if the server is up-to-date and synchronised - * @return if is current - */ - public boolean isCurrent() { - return current; - } - - /** - * Indicates the node is considered offline by the group - * @return if is offline - */ - public boolean isOffline() { - return offline; - } - - /** - * Time since this peer was last seen - * @return the active time - */ - @NonNull - public Duration getActive() { - return active; - } - - /** - * How many uncommitted operations this peer is behind the leader - * @return the lag - */ - public long getLag() { - return lag; - } -} diff --git a/src/test/java/io/nats/client/api/Placement.java b/src/test/java/io/nats/client/api/Placement.java deleted file mode 100644 index e9198a8..0000000 --- a/src/test/java/io/nats/client/api/Placement.java +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; - -import static io.nats.client.support.ApiConstants.CLUSTER; -import static io.nats.client.support.ApiConstants.TAGS; -import static io.synadia.json.JsonValueUtils.readString; -import static io.synadia.json.JsonValueUtils.readStringListOrNull; -import static io.synadia.json.JsonWriteUtils.*; - -/** - * Placement directives to consider when placing replicas of a stream - */ -public class Placement implements JsonSerializable { - private final String cluster; - private final List tags; - - static Placement optionalInstance(JsonValue vPlacement) { - return vPlacement == null ? null : new Placement(vPlacement); - } - - Placement(JsonValue vPlacement) { - this.cluster = readString(vPlacement, CLUSTER); - this.tags = readStringListOrNull(vPlacement, TAGS); - } - - /** - * Construct a placement object - * @param cluster the cluster name - * @param tags the list of tags, may be null - */ - public Placement(String cluster, List tags) { - this.cluster = cluster == null || cluster.isEmpty() ? null : cluster; - this.tags = tags == null || tags.isEmpty() ? null : tags; - } - - public boolean hasData() { - return cluster != null || tags != null; - } - - /** - * The desired cluster name to place the stream. - * @return The cluster name - */ - @Nullable - public String getCluster() { - return cluster; - } - - /** - * Tags required on servers hosting this stream - * @return the list of tags - */ - @Nullable - public List getTags() { - return tags; - } - - @Override - public String toString() { - return "Placement{" + - "cluster='" + cluster + '\'' + - ", tags=" + tags + - '}'; - } - - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, CLUSTER, cluster); - addStrings(sb, TAGS, tags); - return endJson(sb).toString(); - } - - /** - * Creates a builder for a placements object. - * @return the builder. - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Placement can be created using a Builder. - */ - public static class Builder { - private String cluster; - private List tags; - - /** - * Set the cluster string. - * @param cluster the cluster - * @return the builder - */ - public Builder cluster(String cluster) { - this.cluster = cluster; - return this; - } - - /** - * Set the tags - * @param tags the list of tags - * @return the builder - */ - public Builder tags(String... tags) { - this.tags = Arrays.asList(tags); - return this; - } - - /** - * Set the tags - * @param tags the list of tags - * @return the builder - */ - public Builder tags(List tags) { - this.tags = tags; - return this; - } - - /** - * Build a Placement object - * @return the Placement - */ - public Placement build() { - return new Placement(cluster, tags); - } - } -} diff --git a/src/test/java/io/nats/client/api/PriorityPolicy.java b/src/test/java/io/nats/client/api/PriorityPolicy.java deleted file mode 100644 index ac65fe2..0000000 --- a/src/test/java/io/nats/client/api/PriorityPolicy.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Represents the Priority Policy of a consumer - */ -public enum PriorityPolicy { - None("none"), - Overflow("overflow"), - PinnedClient("pinned_client"); - - private final String policy; - - PriorityPolicy(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (PriorityPolicy env : PriorityPolicy.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static PriorityPolicy get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/ReplayPolicy.java b/src/test/java/io/nats/client/api/ReplayPolicy.java deleted file mode 100644 index 6d0953c..0000000 --- a/src/test/java/io/nats/client/api/ReplayPolicy.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Represents the replay policy of a consumer. - */ -public enum ReplayPolicy { - Instant("instant"), - Original("original"); - - private String policy; - - ReplayPolicy(String p) { - this.policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (ReplayPolicy env : ReplayPolicy.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static ReplayPolicy get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/Replica.java b/src/test/java/io/nats/client/api/Replica.java deleted file mode 100644 index 5bdba12..0000000 --- a/src/test/java/io/nats/client/api/Replica.java +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import io.synadia.json.JsonValueUtils; - -import java.util.List; - -public class Replica extends PeerInfo { - - static List optionalListOf(JsonValue vReplicas) { - return JsonValueUtils.listOfOrEmpty(vReplicas, Replica::new); - } - - Replica(JsonValue vReplica) { - super(vReplica); - } - - @Override - public String toString() { - return "Replica{" + - "name='" + getName() + '\'' + - ", current=" + isCurrent() + - ", offline=" + isOffline() + - ", active=" + getActive() + - ", lag=" + getLag() + - '}'; - } -} diff --git a/src/test/java/io/nats/client/api/Republish.java b/src/test/java/io/nats/client/api/Republish.java deleted file mode 100644 index 5504766..0000000 --- a/src/test/java/io/nats/client/api/Republish.java +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.readBoolean; -import static io.synadia.json.JsonValueUtils.readString; -import static io.synadia.json.JsonWriteUtils.*; - -/** - * Republish Configuration - */ -public class Republish implements JsonSerializable { - private final String source; - private final String destination; - private final boolean headersOnly; - - static Republish optionalInstance(JsonValue vRepublish) { - return vRepublish == null ? null : new Republish(vRepublish); - } - - Republish(JsonValue vRepublish) { - source = readString(vRepublish, SRC); - destination = readString(vRepublish, DEST); - headersOnly = readBoolean(vRepublish, HEADERS_ONLY, false); - } - - /** - * Get source, the Published subject matching filter - * @return the source - */ - @NonNull - public String getSource() { - return source; - } - - /** - * Get destination, the RePublish Subject template - * @return the destination - */ - @NonNull - public String getDestination() { - return destination; - } - - /** - * Get headersOnly, Whether to RePublish only headers (no body) - * @return headersOnly - */ - public boolean isHeadersOnly() { - return headersOnly; - } - - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, SRC, source); - addField(sb, DEST, destination); - addField(sb, HEADERS_ONLY, headersOnly); - return endJson(sb).toString(); - } -} diff --git a/src/test/java/io/nats/client/api/RetentionPolicy.java b/src/test/java/io/nats/client/api/RetentionPolicy.java deleted file mode 100644 index c1c5c7f..0000000 --- a/src/test/java/io/nats/client/api/RetentionPolicy.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; - -/** - * Stream retention policies. - */ -public enum RetentionPolicy { - Limits("limits"), - Interest("interest"), - WorkQueue("workqueue"); - - private final String policy; - - RetentionPolicy(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - private static final Map strEnumHash = new HashMap<>(); - - static { - for (RetentionPolicy env : RetentionPolicy.values()) { - strEnumHash.put(env.toString(), env); - } - } - - @Nullable - public static RetentionPolicy get(String value) { - return strEnumHash.get(value); - } -} diff --git a/src/test/java/io/nats/client/api/ServerInfo.java b/src/test/java/io/nats/client/api/ServerInfo.java deleted file mode 100644 index 970b4b8..0000000 --- a/src/test/java/io/nats/client/api/ServerInfo.java +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonParseException; -import io.synadia.json.JsonParser; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; - -public class ServerInfo { - - public static final ServerInfo EMPTY_INFO = new ServerInfo("INFO {}"); - - private final String serverId; - private final String serverName; - private final String version; - private final String go; - private final String host; - private final int port; - private final boolean headersSupported; - private final boolean authRequired; - private final boolean tlsRequired; - private final boolean tlsAvailable; - private final long maxPayload; - private final List connectURLs; - private final int protocolVersion; - private final byte[] nonce; - private final boolean lameDuckMode; - private final boolean jetStream; - private final int clientId; - private final String clientIp; - private final String cluster; - - public ServerInfo(String json) { - // INFO{ INFO<\t>{ or { - if (json == null || json.length() < 6 || ('{' != json.charAt(0) && '{' != json.charAt(5))) { - throw new IllegalArgumentException("Invalid Server Info"); - } - - JsonValue jv; - try { - jv = JsonParser.parse(json, json.indexOf("{")); - } - catch (JsonParseException e) { - throw new IllegalArgumentException("Invalid Server Info Json"); - } - - serverId = readString(jv, SERVER_ID, "UNDEFINED"); - serverName = readString(jv, SERVER_NAME, "UNDEFINED"); - version = readString(jv, VERSION, "0.0.0"); - go = readString(jv, GO, "0.0.0"); - host = readString(jv, HOST, "UNDEFINED"); - headersSupported = readBoolean(jv, HEADERS, false); - authRequired = readBoolean(jv, AUTH_REQUIRED, false); - nonce = readBytes(jv, NONCE); - tlsRequired = readBoolean(jv, TLS_REQUIRED, false); - tlsAvailable = readBoolean(jv, TLS_AVAILABLE, false); - lameDuckMode = readBoolean(jv, LAME_DUCK_MODE, false); - jetStream = readBoolean(jv, JETSTREAM, false); - port = readInteger(jv, PORT, 0); - protocolVersion = readInteger(jv, PROTO, 0); - maxPayload = readLong(jv, MAX_PAYLOAD, 0); - clientId = readInteger(jv, CLIENT_ID, 0); - clientIp = readString(jv, CLIENT_IP, "0.0.0.0"); - cluster = readString(jv, CLUSTER); - connectURLs = readStringListOrEmpty(jv, CONNECT_URLS); - } - - public boolean isLameDuckMode() { - return lameDuckMode; - } - - @NonNull - public String getServerId() { - return this.serverId; - } - - @NonNull - public String getServerName() { - return serverName; - } - - @NonNull - public String getVersion() { - return this.version; - } - - @NonNull - public String getGoVersion() { - return this.go; - } - - @NonNull - public String getHost() { - return this.host; - } - - public int getPort() { - return this.port; - } - - public int getProtocolVersion() { - return this.protocolVersion; - } - - public boolean isHeadersSupported() { return this.headersSupported; } - - public boolean isAuthRequired() { - return this.authRequired; - } - - public boolean isTLSRequired() { - return this.tlsRequired; - } - - public boolean isTLSAvailable() { - return tlsAvailable; - } - - public long getMaxPayload() { - return this.maxPayload; - } - - @NonNull - public List getConnectURLs() { - return this.connectURLs; - } - - public byte @Nullable [] getNonce() { - return this.nonce; - } - - public boolean isJetStreamAvailable() { - return this.jetStream; - } - - public int getClientId() { - return clientId; - } - - @NonNull - public String getClientIp() { - return clientIp; - } - - @Nullable - public String getCluster() { - return cluster; - } - - @Override - public String toString() { - return "ServerInfo{" + - "serverId='" + serverId + '\'' + - ", serverName='" + serverName + '\'' + - ", version='" + version + '\'' + - ", go='" + go + '\'' + - ", host='" + host + '\'' + - ", port=" + port + - ", headersSupported=" + headersSupported + - ", authRequired=" + authRequired + - ", tlsRequired=" + tlsRequired + - ", tlsAvailable=" + tlsAvailable + - ", maxPayload=" + maxPayload + - ", connectURLs=" + connectURLs + - ", protocolVersion=" + protocolVersion + - ", nonce=" + Arrays.toString(nonce) + - ", lameDuckMode=" + lameDuckMode + - ", jetStream=" + jetStream + - ", clientId=" + clientId + - ", clientIp='" + clientIp + '\'' + - ", cluster='" + cluster + '\'' + - '}'; - } -} diff --git a/src/test/java/io/nats/client/api/Source.java b/src/test/java/io/nats/client/api/Source.java deleted file mode 100644 index 1dccb41..0000000 --- a/src/test/java/io/nats/client/api/Source.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - - -import io.synadia.json.JsonValue; -import io.synadia.json.JsonValueUtils; - -import java.util.List; - -/** - * Source Information - */ -public class Source extends SourceBase { - - static List optionalListOf(JsonValue vSources) { - return JsonValueUtils.listOfOrEmpty(vSources, Source::new); - } - - public Source(JsonValue vSource) { - super(vSource); - } -} diff --git a/src/test/java/io/nats/client/api/SourceBase.java b/src/test/java/io/nats/client/api/SourceBase.java deleted file mode 100644 index cb0c17e..0000000 --- a/src/test/java/io/nats/client/api/SourceBase.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; - -import java.time.ZonedDateTime; -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; -import static io.synadia.json.JsonWriteUtils.*; - -public abstract class SourceBase implements JsonSerializable { - private final String name; - private final long startSeq; - private final ZonedDateTime startTime; - private final String filterSubject; - private final External external; - private final List subjectTransforms; - - SourceBase(JsonValue jv) { - name = readString(jv, NAME); - startSeq = readLong(jv, OPT_START_SEQ, 0); - startTime = readDate(jv, OPT_START_TIME); - filterSubject = readString(jv, FILTER_SUBJECT); - external = External.optionalInstance(readValue(jv, EXTERNAL)); - subjectTransforms = SubjectTransform.optionalListOf(readValue(jv, SUBJECT_TRANSFORMS)); - } - - /** - * Returns a JSON representation of this mirror - * @return json mirror json string - */ - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, NAME, name); - addFieldWhenGreaterThan(sb, OPT_START_SEQ, startSeq, 0); - addField(sb, OPT_START_TIME, startTime); - addField(sb, FILTER_SUBJECT, filterSubject); - addField(sb, EXTERNAL, external); - addJsons(sb, SUBJECT_TRANSFORMS, subjectTransforms); - return endJson(sb).toString(); - } -} diff --git a/src/test/java/io/nats/client/api/SourceInfo.java b/src/test/java/io/nats/client/api/SourceInfo.java deleted file mode 100644 index 08a090c..0000000 --- a/src/test/java/io/nats/client/api/SourceInfo.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import io.synadia.json.JsonValueUtils; - -import java.util.List; - -/** - * Information about a stream being sourced - */ -public class SourceInfo extends SourceInfoBase { - - static List optionalListOf(JsonValue vSourceInfos) { - return JsonValueUtils.listOfOrEmpty(vSourceInfos, SourceInfo::new); - } - - SourceInfo(JsonValue vSourceInfo) { - super(vSourceInfo); - } - - @Override - public String toString() { - return "SourceInfo " + jv; - } -} diff --git a/src/test/java/io/nats/client/api/SourceInfoBase.java b/src/test/java/io/nats/client/api/SourceInfoBase.java deleted file mode 100644 index 6f98659..0000000 --- a/src/test/java/io/nats/client/api/SourceInfoBase.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.time.Duration; -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; - -abstract class SourceInfoBase { - protected final JsonValue jv; - protected final String name; - protected final long lag; - protected final Duration active; - protected final External external; - protected final List subjectTransforms; - - SourceInfoBase(JsonValue vSourceInfo) { - jv = vSourceInfo; - name = readString(vSourceInfo, NAME); - lag = readLong(vSourceInfo, LAG, 0); - active = readNanosAsDuration(vSourceInfo, ACTIVE, Duration.ZERO); - external = External.optionalInstance(readValue(vSourceInfo, EXTERNAL)); - subjectTransforms = SubjectTransform.optionalListOf(readValue(vSourceInfo, SUBJECT_TRANSFORMS)); - } - - /** - * The name of the Stream being replicated - * @return the name - */ - @NonNull - public String getName() { - return name; - } - - /** - * How many uncommitted operations this peer is behind the leader - * @return the lag - */ - public long getLag() { - return lag; - } - - /** - * Time since this peer was last seen - * @return the time - */ - @NonNull - public Duration getActive() { - return active; - } - - /** - * Configuration referencing a stream source in another account or JetStream domain - * @return the external - */ - @Nullable - public External getExternal() { - return external; - } - - /** - * The list of subject transforms, if any - * @return the list of subject transforms - */ - @Nullable - public List getSubjectTransforms() { - return subjectTransforms; - } -} diff --git a/src/test/java/io/nats/client/api/StorageType.java b/src/test/java/io/nats/client/api/StorageType.java deleted file mode 100644 index 4b31991..0000000 --- a/src/test/java/io/nats/client/api/StorageType.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import org.jspecify.annotations.Nullable; - -/** - * Stream storage types. - */ -public enum StorageType { - File("file"), - Memory("memory"); - - private final String policy; - - StorageType(String p) { - policy = p; - } - - @Override - public String toString() { - return policy; - } - - @Nullable - public static StorageType get(String value) { - if (File.policy.equalsIgnoreCase(value)) { return File; } - if (Memory.policy.equalsIgnoreCase(value)) { return Memory; } - return null; - } -} diff --git a/src/test/java/io/nats/client/api/StreamAlternate.java b/src/test/java/io/nats/client/api/StreamAlternate.java deleted file mode 100644 index fc6a35d..0000000 --- a/src/test/java/io/nats/client/api/StreamAlternate.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import io.synadia.json.JsonValueUtils; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.readString; - -public class StreamAlternate { - private final String name; - private final String domain; - private final String cluster; - - static List optionalListOf(JsonValue vSourceInfos) { - return JsonValueUtils.listOfOrEmpty(vSourceInfos, StreamAlternate::new); - } - - StreamAlternate(JsonValue vLost) { - name = readString(vLost, NAME); - domain = readString(vLost, DOMAIN); - cluster = readString(vLost, CLUSTER); - } - - /** - * The mirror stream name - * @return the name - */ - @NonNull - public String getName() { - return name; - } - - /** - * The domain - * @return the domain - */ - @Nullable - public String getDomain() { - return domain; - } - - /** - * The name of the cluster holding the stream - * @return the cluster - */ - @NonNull - public String getCluster() { - return cluster; - } -} diff --git a/src/test/java/io/nats/client/api/StreamConfiguration.java b/src/test/java/io/nats/client/api/StreamConfiguration.java deleted file mode 100644 index 470a745..0000000 --- a/src/test/java/io/nats/client/api/StreamConfiguration.java +++ /dev/null @@ -1,819 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonParseException; -import io.synadia.json.JsonParser; -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; - -import java.time.Duration; -import java.util.*; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; -import static io.synadia.json.JsonWriteUtils.*; - -public class StreamConfiguration implements JsonSerializable { - - // see builder for defaults - private final String name; - private final String description; - private final List subjects; - private final RetentionPolicy retentionPolicy; - private final CompressionOption compressionOption; - private final long maxConsumers; - private final long maxMsgs; - private final long maxMsgsPerSubject; - private final long maxBytes; - private final Duration maxAge; - private final int maxMsgSize; - private final StorageType storageType; - private final int replicas; - private final boolean noAck; - private final String templateOwner; - private final DiscardPolicy discardPolicy; - private final Duration duplicateWindow; - private final Placement placement; - private final Republish republish; - private final SubjectTransform subjectTransform; - private final ConsumerLimits consumerLimits; - private final Mirror mirror; - private final List sources; - private final boolean sealed; - private final boolean allowRollup; - private final boolean allowDirect; - private final boolean mirrorDirect; - private final boolean denyDelete; - private final boolean denyPurge; - private final boolean discardNewPerSubject; - private final Map metadata; - private final long firstSequence; - private final boolean allowMessageTtl; - private final Duration subjectDeleteMarkerTtl; - - static StreamConfiguration instance(JsonValue v) { - return new Builder() - .retentionPolicy(RetentionPolicy.get(readString(v, RETENTION))) - .compressionOption(CompressionOption.get(readString(v, COMPRESSION))) - .storageType(StorageType.get(readString(v, STORAGE))) - .discardPolicy(DiscardPolicy.get(readString(v, DISCARD))) - .name(readString(v, NAME)) - .description(readString(v, DESCRIPTION)) - .maxConsumers(readLong(v, MAX_CONSUMERS, -1)) - .maxMessages(readLong(v, MAX_MSGS, -1)) - .maxMessagesPerSubject(readLong(v, MAX_MSGS_PER_SUB, -1)) - .maxBytes(readLong(v, MAX_BYTES, -1)) - .maxAge(readNanosAsDuration(v, MAX_AGE)) - .maximumMessageSize(readInteger(v, MAX_MSG_SIZE, -1)) - .replicas(readInteger(v, NUM_REPLICAS, 1)) - .noAck(readBoolean(v, NO_ACK, false)) - .templateOwner(readString(v, TEMPLATE_OWNER)) - .duplicateWindow(readNanosAsDuration(v, DUPLICATE_WINDOW)) - .subjects(readStringListOrEmpty(v, SUBJECTS)) - .placement(Placement.optionalInstance(readValue(v, PLACEMENT))) - .republish(Republish.optionalInstance(readValue(v, REPUBLISH))) - .subjectTransform(SubjectTransform.optionalInstance(readValue(v, SUBJECT_TRANSFORM))) - .consumerLimits(ConsumerLimits.optionalInstance(readValue(v, CONSUMER_LIMITS))) - .mirror(Mirror.optionalInstance(readValue(v, MIRROR))) - .sources(Source.optionalListOf(readValue(v, SOURCES))) - .sealed(readBoolean(v, SEALED, false)) - .allowRollup(readBoolean(v, ALLOW_ROLLUP_HDRS, false)) - .allowDirect(readBoolean(v, ALLOW_DIRECT, false)) - .mirrorDirect(readBoolean(v, MIRROR_DIRECT, false)) - .denyDelete(readBoolean(v, DENY_DELETE, false)) - .denyPurge(readBoolean(v, DENY_PURGE, false)) - .discardNewPerSubject(readBoolean(v, DISCARD_NEW_PER_SUBJECT, false)) - .metadata(readStringMapOrNull(v, METADATA)) - .firstSequence(readLong(v, FIRST_SEQ, 1)) - .allowMessageTtl(readBoolean(v, ALLOW_MSG_TTL, false)) - .subjectDeleteMarkerTtl(readNanosAsDuration(v, SUBJECT_DELETE_MARKER_TTL)) - .build(); - } - - // For the builder, assumes all validations are already done in builder - StreamConfiguration(Builder b) { - this.name = b.name; - this.description = b.description; - this.subjects = b.subjects; - this.retentionPolicy = b.retentionPolicy; - this.compressionOption = b.compressionOption; - this.maxConsumers = b.maxConsumers; - this.maxMsgs = b.maxMsgs; - this.maxMsgsPerSubject = b.maxMsgsPerSubject; - this.maxBytes = b.maxBytes; - this.maxAge = b.maxAge; - this.maxMsgSize = b.maxMsgSize; - this.storageType = b.storageType; - this.replicas = b.replicas; - this.noAck = b.noAck; - this.templateOwner = b.templateOwner; - this.discardPolicy = b.discardPolicy; - this.duplicateWindow = b.duplicateWindow; - this.placement = b.placement; - this.republish = b.republish; - this.subjectTransform = b.subjectTransform; - this.consumerLimits = b.consumerLimits; - this.mirror = b.mirror; - this.sources = b.sources; - this.sealed = b.sealed; - this.allowRollup = b.allowRollup; - this.allowDirect = b.allowDirect; - this.mirrorDirect = b.mirrorDirect; - this.denyDelete = b.denyDelete; - this.denyPurge = b.denyPurge; - this.discardNewPerSubject = b.discardNewPerSubject; - this.metadata = b.metadata; - this.firstSequence = b.firstSequence; - this.allowMessageTtl = b.allowMessageTtl; - this.subjectDeleteMarkerTtl = b.subjectDeleteMarkerTtl; - } - - /** - * Returns a StreamConfiguration deserialized from its JSON form. - * - * @see #toJson() - * @param json the json representing the Stream Configuration - * @return StreamConfiguration for the given json - * @throws JsonParseException thrown if the parsing fails for invalid json - */ - public static StreamConfiguration instance(String json) throws JsonParseException { - return instance(JsonParser.parse(json)); - } - - /** - * Returns a JSON representation of this consumer configuration. - * - * @return json consumer configuration to send to the server. - */ - @Override - @NonNull - public String toJson() { - - StringBuilder sb = beginJson(); - - addField(sb, NAME, name); - addField(sb, DESCRIPTION, description); - addStrings(sb, SUBJECTS, subjects); - addField(sb, RETENTION, retentionPolicy.toString()); - addEnumWhenNot(sb, COMPRESSION, compressionOption, CompressionOption.None); - addField(sb, MAX_CONSUMERS, maxConsumers); - addField(sb, MAX_MSGS, maxMsgs); - addField(sb, MAX_MSGS_PER_SUB, maxMsgsPerSubject); - addField(sb, MAX_BYTES, maxBytes); - addFieldAsNanos(sb, MAX_AGE, maxAge); - addField(sb, MAX_MSG_SIZE, maxMsgSize); - addField(sb, STORAGE, storageType.toString()); - addField(sb, NUM_REPLICAS, replicas); - addField(sb, NO_ACK, noAck); - addField(sb, TEMPLATE_OWNER, templateOwner); - addField(sb, DISCARD, discardPolicy.toString()); - addFieldAsNanos(sb, DUPLICATE_WINDOW, duplicateWindow); - if (placement != null && placement.hasData()) { - addField(sb, PLACEMENT, placement); - } - addField(sb, REPUBLISH, republish); - addField(sb, SUBJECT_TRANSFORM, subjectTransform); - addField(sb, CONSUMER_LIMITS, consumerLimits); - addField(sb, MIRROR, mirror); - addJsons(sb, SOURCES, sources); - addField(sb, SEALED, sealed); - addField(sb, ALLOW_ROLLUP_HDRS, allowRollup); - addField(sb, ALLOW_DIRECT, allowDirect); - addField(sb, MIRROR_DIRECT, mirrorDirect); - addField(sb, DENY_DELETE, denyDelete); - addField(sb, DENY_PURGE, denyPurge); - addField(sb, DISCARD_NEW_PER_SUBJECT, discardNewPerSubject); - addField(sb, METADATA, metadata); - addFieldWhenGreaterThan(sb, FIRST_SEQ, firstSequence, 1); - addField(sb, ALLOW_MSG_TTL, allowMessageTtl); - addFieldAsNanos(sb, SUBJECT_DELETE_MARKER_TTL, subjectDeleteMarkerTtl); - - return endJson(sb).toString(); - } - - /** - * Creates a builder for the stream configuration. - * @return a stream configuration builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Creates a builder to copy the stream configuration. - * @param sc an existing StreamConfiguration - * @return a stream configuration builder - */ - public static Builder builder(StreamConfiguration sc) { - return new Builder(sc); - } - - /** - * StreamConfiguration is created using a Builder. The builder supports chaining and will - * create a default set of options if no methods are calls. - * - *

{@code new StreamConfiguration.Builder().build()} will create a new StreamConfiguration. - * - */ - @SuppressWarnings("UnusedReturnValue") - public static class Builder { - - private String name = null; - private String description = null; - private final List subjects = new ArrayList<>(); - private RetentionPolicy retentionPolicy = RetentionPolicy.Limits; - private CompressionOption compressionOption = CompressionOption.None; - private long maxConsumers = -1; - private long maxMsgs = -1; - private long maxMsgsPerSubject = -1; - private long maxBytes = -1; - private Duration maxAge = Duration.ZERO; - private int maxMsgSize = -1; - private StorageType storageType = StorageType.File; - private int replicas = 1; - private boolean noAck = false; - private String templateOwner = null; - private DiscardPolicy discardPolicy = DiscardPolicy.Old; - private Duration duplicateWindow = Duration.ZERO; - private Placement placement = null; - private Republish republish = null; - private SubjectTransform subjectTransform = null; - private ConsumerLimits consumerLimits = null; - private Mirror mirror = null; - private final List sources = new ArrayList<>(); - private boolean sealed = false; - private boolean allowRollup = false; - private boolean allowDirect = false; - private boolean mirrorDirect = false; - private boolean denyDelete = false; - private boolean denyPurge = false; - private boolean discardNewPerSubject = false; - private Map metadata; - private long firstSequence = 1; - private boolean allowMessageTtl = false; - private Duration subjectDeleteMarkerTtl; - - /** - * Default Builder - */ - public Builder() {} - - /** - * Update Builder, useful if you need to update a configuration - * @param sc the configuration to copy - */ - public Builder(StreamConfiguration sc) { - if (sc != null) { - this.name = sc.name; - this.description = sc.description; - subjects(sc.subjects); - this.retentionPolicy = sc.retentionPolicy; - this.compressionOption = sc.compressionOption; - this.maxConsumers = sc.maxConsumers; - this.maxMsgs = sc.maxMsgs; - this.maxMsgsPerSubject = sc.maxMsgsPerSubject; - this.maxBytes = sc.maxBytes; - this.maxAge = sc.maxAge; - this.maxMsgSize = sc.maxMsgSize; - this.storageType = sc.storageType; - this.replicas = sc.replicas; - this.noAck = sc.noAck; - this.templateOwner = sc.templateOwner; - this.discardPolicy = sc.discardPolicy; - this.duplicateWindow = sc.duplicateWindow; - this.placement = sc.placement; - this.republish = sc.republish; - this.subjectTransform = sc.subjectTransform; - this.consumerLimits = sc.consumerLimits; - this.mirror = sc.mirror; - sources(sc.sources); - this.sealed = sc.sealed; - this.allowRollup = sc.allowRollup; - this.allowDirect = sc.allowDirect; - this.mirrorDirect = sc.mirrorDirect; - this.denyDelete = sc.denyDelete; - this.denyPurge = sc.denyPurge; - this.discardNewPerSubject = sc.discardNewPerSubject; - if (sc.metadata != null) { - this.metadata = new HashMap<>(sc.metadata); - } - this.firstSequence = sc.firstSequence; - this.allowMessageTtl = sc.allowMessageTtl; - this.subjectDeleteMarkerTtl = sc.subjectDeleteMarkerTtl; - } - } - - /** - * Sets the name of the stream. - * @param name name of the stream. - * @return the builder - */ - public Builder name(String name) { - this.name = name; - return this; - } - - /** - * Sets the description - * @param description the description - * @return the builder - */ - public Builder description(String description) { - this.description = description; - return this; - } - - /** - * Sets the subjects in the StreamConfiguration. - * @param subjects the stream's subjects - * @return The Builder - */ - public Builder subjects(String... subjects) { - this.subjects.clear(); - return addSubjects(subjects); - } - - /** - * Sets the subjects in the StreamConfiguration. - * @param subjects the stream's subjects - * @return The Builder - */ - public Builder subjects(Collection subjects) { - this.subjects.clear(); - return addSubjects(subjects); - } - - /** - * Adds unique subjects into the StreamConfiguration. - * @param subjects the stream's subjects to add - * @return The Builder - */ - public Builder addSubjects(String... subjects) { - if (subjects != null) { - return addSubjects(Arrays.asList(subjects)); - } - return this; - } - - /** - * Adds unique subjects into the StreamConfiguration. - * @param subjects the stream's subjects to add - * @return The Builder - */ - public Builder addSubjects(Collection subjects) { - if (subjects != null) { - for (String sub : subjects) { - if (sub != null && !this.subjects.contains(sub)) { - this.subjects.add(sub); - } - } - } - return this; - } - - /** - * Sets the retention policy in the StreamConfiguration. - * @param policy the retention policy of the StreamConfiguration - * @return The Builder - */ - public Builder retentionPolicy(RetentionPolicy policy) { - this.retentionPolicy = policy == null ? RetentionPolicy.Limits : policy; - return this; - } - - /** - * Sets the compression option in the StreamConfiguration. - * @param compressionOption the compression option of the StreamConfiguration - * @return The Builder - */ - public Builder compressionOption(CompressionOption compressionOption) { - this.compressionOption = compressionOption == null ? CompressionOption.None : compressionOption; - return this; - } - - /** - * Sets the maximum number of consumers in the StreamConfiguration. - * @param maxConsumers the maximum number of consumers - * @return The Builder - */ - public Builder maxConsumers(long maxConsumers) { - this.maxConsumers = maxConsumers; - return this; - } - - /** - * Sets the maximum number of messages in the StreamConfiguration. - * @param maxMsgs the maximum number of messages - * @return The Builder - */ - public Builder maxMessages(long maxMsgs) { - this.maxMsgs = maxMsgs; - return this; - } - - /** - * Sets the maximum number of message per subject in the StreamConfiguration. - * @param maxMsgsPerSubject the maximum number of messages - * @return The Builder - */ - public Builder maxMessagesPerSubject(long maxMsgsPerSubject) { - this.maxMsgsPerSubject = maxMsgsPerSubject; - return this; - } - - /** - * Sets the maximum number of bytes in the StreamConfiguration. - * @param maxBytes the maximum number of bytes - * @return The Builder - */ - public Builder maxBytes(long maxBytes) { - this.maxBytes = maxBytes; - return this; - } - - /** - * Sets the maximum age in the StreamConfiguration. - * @param maxAge the maximum message age - * @return The Builder - */ - public Builder maxAge(Duration maxAge) { - this.maxAge = maxAge; - return this; - } - - /** - * Sets the maximum age in the StreamConfiguration. - * @param maxAgeMillis the maximum message age - * @return The Builder - */ - public Builder maxAge(long maxAgeMillis) { - this.maxAge = Duration.ofMillis(maxAgeMillis); - return this; - } - - /** - * Sets the maximum message size in the StreamConfiguration. - * @deprecated the server value is a 32-bit signed value. Use {@link #maximumMessageSize(int)} instead. - * @param maxMsgSize the maximum message size - * @return The Builder - */ - @Deprecated - public Builder maxMsgSize(long maxMsgSize) { - this.maxMsgSize = (int)maxMsgSize; - return this; - } - - /** - * Sets the maximum message size in the StreamConfiguration. - * @param maxMsgSize the maximum message size - * @return The Builder - */ - public Builder maximumMessageSize(int maxMsgSize) { - this.maxMsgSize = (int)maxMsgSize; - return this; - } - - /** - * Sets the storage type in the StreamConfiguration. - * @param storageType the storage type - * @return The Builder - */ - public Builder storageType(StorageType storageType) { - this.storageType = storageType == null ? StorageType.File : storageType; - return this; - } - - /** - * Sets the number of replicas a message must be stored on in the StreamConfiguration. - * Must be 1 to 5 inclusive - * @param replicas the number of replicas to store this message on - * @return The Builder - */ - public Builder replicas(int replicas) { - this.replicas = replicas; - return this; - } - - /** - * Sets the acknowledgement mode of the StreamConfiguration. if no acknowledgements are - * set, then acknowledgements are not sent back to the client. The default is false. - * @param noAck true to disable acknowledgements. - * @return The Builder - */ - public Builder noAck(boolean noAck) { - this.noAck = noAck; - return this; - } - - /** - * Sets the template a stream in the form of raw JSON. - * @param templateOwner the stream template of the stream. - * @return the builder - */ - public Builder templateOwner(String templateOwner) { - this.templateOwner = templateOwner == null || templateOwner.trim().isEmpty() ? null : templateOwner; - return this; - } - - /** - * Sets the discard policy in the StreamConfiguration. - * @param policy the discard policy of the StreamConfiguration - * @return The Builder - */ - public Builder discardPolicy(DiscardPolicy policy) { - this.discardPolicy = policy == null ? DiscardPolicy.Old : policy; - return this; - } - - /** - * Sets the duplicate checking window in the StreamConfiguration. A Duration.Zero - * disables duplicate checking. Duplicate checking is disabled by default. - * @param window duration to hold message ids for duplicate checking. - * @return The Builder - */ - public Builder duplicateWindow(Duration window) { - this.duplicateWindow = window; - return this; - } - - /** - * Sets the duplicate checking window in the StreamConfiguration. A Duration.Zero - * disables duplicate checking. Duplicate checking is disabled by default. - * @param windowMillis duration to hold message ids for duplicate checking. - * @return The Builder - */ - public Builder duplicateWindow(long windowMillis) { - this.duplicateWindow = Duration.ofMillis(windowMillis); - return this; - } - - /** - * Sets the placement directive object - * @param placement the placement directive object - * @return The Builder - */ - public Builder placement(Placement placement) { - this.placement = placement; - return this; - } - - /** - * Sets the republish config object - * @param republish the republish config object - * @return The Builder - */ - public Builder republish(Republish republish) { - this.republish = republish; - return this; - } - - /** - * Sets the subjectTransform config object - * @param subjectTransform the subjectTransform config object - * @return The Builder - */ - public Builder subjectTransform(SubjectTransform subjectTransform) { - this.subjectTransform = subjectTransform; - return this; - } - - /** - * Sets the consumerLimits config object - * @param consumerLimits the consumerLimits config object - * @return The Builder - */ - public Builder consumerLimits(ConsumerLimits consumerLimits) { - this.consumerLimits = consumerLimits; - return this; - } - - /** - * Sets the mirror object - * @param mirror the mirror object - * @return The Builder - */ - public Builder mirror(Mirror mirror) { - this.mirror = mirror; - return this; - } - - /** - * Sets the sources in the StreamConfiguration. - * @param sources the stream's sources - * @return The Builder - */ - public Builder sources(Source... sources) { - this.sources.clear(); - return addSources(sources); - } - - /** - * Add the sources into the StreamConfiguration. - * @param sources the stream's sources - * @return The Builder - */ - public Builder sources(Collection sources) { - this.sources.clear(); - return addSources(sources); - } - - /** - * Add the sources into the StreamConfiguration. - * @param sources the stream's sources - * @return The Builder - */ - public Builder addSources(Source... sources) { - return addSources(Arrays.asList(sources)); - } - - /** - * Sets the sources in the StreamConfiguration. - * @param sources the stream's sources - * @return The Builder - */ - public Builder addSources(Collection sources) { - if (sources != null) { - for (Source source : sources) { - if (source != null && !this.sources.contains(source)) { - this.sources.add(source); - } - } - } - return this; - } - - /** - * Add a source into the StreamConfiguration. - * @param source a stream source - * @return The Builder - */ - public Builder addSource(Source source) { - if (source != null && !this.sources.contains(source)) { - this.sources.add(source); - } - return this; - } - - /** - * Set whether to seal the stream. - * INTERNAL USE ONLY. Scoped protected for test purposes. - * @param sealed the sealed setting - * @return The Builder - */ - protected Builder sealed(boolean sealed) { - this.sealed = sealed; - return this; - } - - /** - * Set whether to allow the rollup feature for a stream - * @param allowRollup the allow rollup setting - * @return The Builder - */ - public Builder allowRollup(boolean allowRollup) { - this.allowRollup = allowRollup; - return this; - } - - /** - * Set whether to allow direct message access for a stream - * @param allowDirect the allow direct setting - * @return The Builder - */ - public Builder allowDirect(boolean allowDirect) { - this.allowDirect = allowDirect; - return this; - } - - /** - * Set whether to allow unified direct access for mirrors - * @param mirrorDirect the allow direct setting - * @return The Builder - */ - public Builder mirrorDirect(boolean mirrorDirect) { - this.mirrorDirect = mirrorDirect; - return this; - } - - /** - * Set whether to deny deleting messages from the stream - * @param denyDelete the deny delete setting - * @return The Builder - */ - public Builder denyDelete(boolean denyDelete) { - this.denyDelete = denyDelete; - return this; - } - - /** - * Set whether to deny purging messages from the stream - * @param denyPurge the deny purge setting - * @return The Builder - */ - public Builder denyPurge(boolean denyPurge) { - this.denyPurge = denyPurge; - return this; - } - - /** - * Set whether discard policy new with max message per subject applies to existing subjects, not just new subjects. - * @param discardNewPerSubject the setting - * @return The Builder - */ - public Builder discardNewPerSubject(boolean discardNewPerSubject) { - this.discardNewPerSubject = discardNewPerSubject; - return this; - } - - /** - * Set this stream to be sealed. This is irreversible. - * @return The Builder - */ - public Builder seal() { - this.sealed = true; - return this; - } - - /** - * Sets the metadata for the configuration - * @param metadata the metadata map - * @return The Builder - */ - public Builder metadata(Map metadata) { - this.metadata = metadata; - return this; - } - - /** - * Sets the first sequence to be used. 1 is the default. All values less than 2 are treated as 1. - * @param firstSeq specify the first_seq in the stream config when creating the stream. - * @return The Builder - */ - public Builder firstSequence(long firstSeq) { - this.firstSequence = firstSeq > 1 ? firstSeq : 1; - return this; - } - - /** - * Set to allow per message TTL to true - * @return The Builder - */ - public Builder allowMessageTtl() { - this.allowMessageTtl = true; - return this; - } - - /** - * Set allow per message TTL flag - * @param allowMessageTtl the flag - * @return The Builder - */ - public Builder allowMessageTtl(boolean allowMessageTtl) { - this.allowMessageTtl = allowMessageTtl; - return this; - } - - /** - * Set the subject delete marker TTL duration. Server accepts 1 second or more. - * null has the effect of clearing the subject delete marker TTL - * @param subjectDeleteMarkerTtl the TTL duration - * @return The Builder - */ - public Builder subjectDeleteMarkerTtl(Duration subjectDeleteMarkerTtl) { - this.subjectDeleteMarkerTtl = subjectDeleteMarkerTtl; - return this; - } - - /** - * Set the subject delete marker TTL duration in milliseconds. Server accepts 1 second or more. - * 0 or less has the effect of clearing the subject delete marker TTL - * @param subjectDeleteMarkerTtlMillis the TTL duration in milliseconds - * @return The Builder - */ - public Builder subjectDeleteMarkerTtl(long subjectDeleteMarkerTtlMillis) { - this.subjectDeleteMarkerTtl = Duration.ofMillis(subjectDeleteMarkerTtlMillis); - return this; - } - - /** - * Builds the StreamConfiguration - * @return a stream configuration. - */ - public StreamConfiguration build() { - return new StreamConfiguration(this); - } - } -} diff --git a/src/test/java/io/nats/client/api/StreamInfo.java b/src/test/java/io/nats/client/api/StreamInfo.java deleted file mode 100644 index 013cb0a..0000000 --- a/src/test/java/io/nats/client/api/StreamInfo.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.time.ZonedDateTime; -import java.util.List; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.readDate; -import static io.synadia.json.JsonValueUtils.readValue; - -/** - * The StreamInfo class contains information about a JetStream stream. - */ -public class StreamInfo extends ApiResponse { - - private final ZonedDateTime createTime; - private final StreamConfiguration config; - private final StreamState streamState; - private final ClusterInfo clusterInfo; - private final MirrorInfo mirrorInfo; - private final List sourceInfos; - private final List alternates; - private final ZonedDateTime timestamp; - - public StreamInfo(JsonValue vStreamInfo) { - super(vStreamInfo); - JsonValue jvConfig = readValue(jv, CONFIG); - config = StreamConfiguration.instance(jvConfig); - - createTime = readDate(jv, CREATED); - - streamState = new StreamState(readValue(jv, STATE)); - clusterInfo = ClusterInfo.optionalInstance(readValue(jv, CLUSTER)); - mirrorInfo = MirrorInfo.optionalInstance(readValue(jv, MIRROR)); - sourceInfos = SourceInfo.optionalListOf(readValue(jv, SOURCES)); - alternates = StreamAlternate.optionalListOf(readValue(jv, ALTERNATES)); - timestamp = readDate(jv, TIMESTAMP); - } - - /** - * Gets the stream configuration. Same as getConfig - * @return the stream configuration. - */ - @NonNull - public StreamConfiguration getConfiguration() { - return config; - } - - /** - * Gets the stream configuration. Same as getConfiguration - * @return the stream configuration. - */ - @NonNull - public StreamConfiguration getConfig() { - return config; - } - - /** - * Gets the stream state. - * @return the stream state - */ - @NonNull - public StreamState getStreamState() { - return streamState; - } - - /** - * Gets the creation time of the stream. - * @return the creation date and time. - */ - @NonNull - public ZonedDateTime getCreateTime() { - return createTime; - } - - @Nullable - public MirrorInfo getMirrorInfo() { - return mirrorInfo; - } - - @Nullable - public List getSourceInfos() { - return sourceInfos; - } - - @Nullable - public ClusterInfo getClusterInfo() { - return clusterInfo; - } - - @Nullable - public List getAlternates() { - return alternates; - } - - /** - * Gets the server time the info was gathered - * @return the server gathered timed - */ - @Nullable // doesn't exist in some versions of the server - public ZonedDateTime getTimestamp() { - return timestamp; - } - - @Override - public String toString() { - return "StreamInfo " + jv; - } -} diff --git a/src/test/java/io/nats/client/api/StreamState.java b/src/test/java/io/nats/client/api/StreamState.java deleted file mode 100644 index 8883819..0000000 --- a/src/test/java/io/nats/client/api/StreamState.java +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; - -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static io.nats.client.support.ApiConstants.*; -import static io.synadia.json.JsonValueUtils.*; - -public class StreamState { - private final long msgs; - private final long bytes; - private final long firstSeq; - private final long lastSeq; - private final long consumerCount; - private final long subjectCount; - private final long deletedCount; - private final ZonedDateTime firstTime; - private final ZonedDateTime lastTime; - private final List subjects; - private final List deletedStreamSequences; - private final LostStreamData lostStreamData; - private final Map subjectMap; - - StreamState(JsonValue vStreamState) { - msgs = readLong(vStreamState, MESSAGES, 0); - bytes = readLong(vStreamState, BYTES, 0); - firstSeq = readLong(vStreamState, FIRST_SEQ, 0); - lastSeq = readLong(vStreamState, LAST_SEQ, 0); - consumerCount = readLong(vStreamState, CONSUMER_COUNT, 0); - firstTime = readDate(vStreamState, FIRST_TS); - lastTime = readDate(vStreamState, LAST_TS); - subjectCount = readLong(vStreamState, NUM_SUBJECTS, 0); - deletedCount = readLong(vStreamState, NUM_DELETED, 0); - deletedStreamSequences = readLongListOrEmpty(vStreamState, DELETED); - lostStreamData = LostStreamData.optionalInstance(readValue(vStreamState, LOST)); - - subjects = new ArrayList<>(); - subjectMap = new HashMap<>(); - JsonValue vSubjects = readValue(vStreamState, SUBJECTS); - if (vSubjects != null && vSubjects.map != null) { - for (String subject : vSubjects.map.keySet()) { - Long count = getLong(vSubjects.map.get(subject)); - if (count != null) { - subjects.add(new Subject(subject, count)); - subjectMap.put(subject, count); - } - } - } - } - - /** - * Gets the message count of the stream. - * - * @return the message count - */ - public long getMsgCount() { - return msgs; - } - - /** - * Gets the byte count of the stream. - * - * @return the byte count - */ - public long getByteCount() { - return bytes; - } - - /** - * Gets the first sequence number of the stream. May be 0 if there are no messages. - * @return a sequence number - */ - public long getFirstSequence() { - return firstSeq; - } - - /** - * Gets the time stamp of the first message in the stream - * - * @return the first time - */ - @Nullable - public ZonedDateTime getFirstTime() { - return firstTime; - } - - /** - * Gets the last sequence of a message in the stream - * - * @return a sequence number - */ - public long getLastSequence() { - return lastSeq; - } - - /** - * Gets the time stamp of the last message in the stream - * - * @return the first time - */ - @Nullable - public ZonedDateTime getLastTime() { - return lastTime; - } - - /** - * Gets the number of consumers attached to the stream. - * - * @return the consumer count - */ - public long getConsumerCount() { - return consumerCount; - } - - /** - * Gets the count of subjects in the stream. - * - * @return the subject count - */ - public long getSubjectCount() { - return subjectCount; - } - - /** - * Get a list of the Subject objects. May be empty, for instance - * if the Stream Info request did not ask for subjects or if there are no subjects. - * @return the list of subjects - */ - @NonNull - public List getSubjects() { - return subjects; - } - - /** - * Get a map of subjects instead of a list of Subject objects. May be empty. - * @return the map - */ - @NonNull - public Map getSubjectMap() { - return subjectMap; - } - - /** - * Gets the count of deleted messages - * - * @return the deleted count - */ - public long getDeletedCount() { - return deletedCount; - } - - /** - * Get a list of the Deleted objects. May be empty if the Stream Info request did not ask for subjects - * or if there are no subjects. - * @return the list of subjects - */ - @NonNull - public List getDeleted() { - return deletedStreamSequences; - } - - /** - * Get the lost stream data information if available. - * @return the LostStreamData - */ - @Nullable - public LostStreamData getLostStreamData() { - return lostStreamData; - } - - @Override - public String toString() { - return "StreamState{" + - "msgs=" + msgs + - ", bytes=" + bytes + - ", firstSeq=" + firstSeq + - ", lastSeq=" + lastSeq + - ", consumerCount=" + consumerCount + - ", firstTime=" + firstTime + - ", lastTime=" + lastTime + - ", subjectCount=" + subjectCount + - ", subjects=" + subjects + - ", deletedCount=" + deletedCount + - ", deleteds=" + deletedStreamSequences + - ", lostStreamData=" + lostStreamData + - '}'; - } -} diff --git a/src/test/java/io/nats/client/api/Subject.java b/src/test/java/io/nats/client/api/Subject.java deleted file mode 100644 index 9c8c7ca..0000000 --- a/src/test/java/io/nats/client/api/Subject.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonValue; -import org.jspecify.annotations.NonNull; - -import java.util.ArrayList; -import java.util.List; - -import static io.synadia.json.JsonValueUtils.getLong; - -public class Subject implements Comparable { - private final String name; - private final long count; - - public static List listOf(JsonValue vSubjects) { - List list = new ArrayList<>(); - if (vSubjects != null && vSubjects.map != null) { - for (String subject : vSubjects.map.keySet()) { - Long count = getLong(vSubjects.map.get(subject)); - if (count != null) { - list.add(new Subject(subject, count)); - } - } - } - return list; - } - - public Subject(String name, long count) { - this.name = name; - this.count = count; - } - - /** - * Get the subject name - * @return the subject - */ - @NonNull - public String getName() { - return name; - } - - /** - * Get the subject message count - * @return the count - */ - public long getCount() { - return count; - } - - @Override - public String toString() { - return "Subject{" + - "name='" + name + '\'' + - ", count=" + count + - '}'; - } - - @Override - public int compareTo(Subject o) { - return name.compareTo(o.name); - } -} diff --git a/src/test/java/io/nats/client/api/SubjectTransform.java b/src/test/java/io/nats/client/api/SubjectTransform.java deleted file mode 100644 index dcb4eaa..0000000 --- a/src/test/java/io/nats/client/api/SubjectTransform.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.api; - -import io.synadia.json.JsonSerializable; -import io.synadia.json.JsonValue; -import io.synadia.json.JsonValueUtils; -import org.jspecify.annotations.NonNull; - -import java.util.List; - -import static io.nats.client.support.ApiConstants.DEST; -import static io.nats.client.support.ApiConstants.SRC; -import static io.synadia.json.JsonValueUtils.readString; -import static io.synadia.json.JsonWriteUtils.*; - -/** - * SubjectTransform - */ -public class SubjectTransform implements JsonSerializable { - private final String source; - private final String destination; - - static SubjectTransform optionalInstance(JsonValue vSubjectTransform) { - return vSubjectTransform == null ? null : new SubjectTransform(vSubjectTransform); - } - - static List optionalListOf(JsonValue vSubjectTransforms) { - return JsonValueUtils.listOfOrEmpty(vSubjectTransforms, SubjectTransform::new); - } - - public SubjectTransform(JsonValue vSubjectTransform) { - source = readString(vSubjectTransform, SRC); - destination = readString(vSubjectTransform, DEST); - } - - /** - * Get source, the subject matching filter - * @return the source - */ - @NonNull - public String getSource() { - return source; - } - - /** - * Get destination, the SubjectTransform Subject template - * @return the destination - */ - @NonNull - public String getDestination() { - return destination; - } - - @Override - @NonNull - public String toJson() { - StringBuilder sb = beginJson(); - addField(sb, SRC, source); - addField(sb, DEST, destination); - return endJson(sb).toString(); - } -} diff --git a/src/test/java/io/nats/client/support/ApiConstants.java b/src/test/java/io/nats/client/support/ApiConstants.java deleted file mode 100644 index 9df9d1d..0000000 --- a/src/test/java/io/nats/client/support/ApiConstants.java +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.support; - -public interface ApiConstants { - - String ACK_FLOOR = "ack_floor"; - String ACK_POLICY = "ack_policy"; - String ACK_WAIT = "ack_wait"; - String ACTION = "action"; - String ACTIVE = "active"; - String ALLOW_DIRECT = "allow_direct"; - String ALLOW_MSG_TTL = "allow_msg_ttl"; - String ALLOW_ROLLUP_HDRS = "allow_rollup_hdrs"; - String ALTERNATES = "alternates"; - String API = "api"; - String API_URL = "api_url"; - String AUTH_REQUIRED = "auth_required"; - String AVERAGE_PROCESSING_TIME = "average_processing_time"; - String BACKOFF = "backoff"; - String BATCH = "batch"; - String BUCKET = "bucket"; - String BYTES = "bytes"; - String CHUNKS = "chunks"; - String CLIENT_ID = "client_id"; - String CLIENT_IP = "client_ip"; - String CLUSTER = "cluster"; - String CODE = "code"; - String COMPRESSION = "compression"; - String CONFIG = "config"; - String CONNECT_URLS = "connect_urls"; - String CONSUMER_COUNT = "consumer_count"; - String CONSUMER_SEQ = "consumer_seq"; - String CONSUMER_LIMITS = "consumer_limits"; - String CONSUMERS = "consumers"; - String CREATED = "created"; - String CURRENT = "current"; - String DATA = "data"; - String DELETED = "deleted"; - String DELETED_DETAILS = "deleted_details"; - String DELIVER = "deliver"; - String DELIVER_GROUP = "deliver_group"; - String DELIVER_POLICY = "deliver_policy"; - String DELIVER_SUBJECT = "deliver_subject"; - String DELIVERED = "delivered"; - String DENY_DELETE = "deny_delete"; - String DENY_PURGE = "deny_purge"; - String DESCRIPTION = "description"; - String DEST = "dest"; - String DIGEST = "digest"; - String DISCARD = "discard"; - String DISCARD_NEW_PER_SUBJECT = "discard_new_per_subject"; - String DOMAIN = "domain"; - String DUPLICATE = "duplicate"; - String DUPLICATE_WINDOW = "duplicate_window"; - String ENDPOINTS = "endpoints"; - String DURABLE_NAME = "durable_name"; - String ERR_CODE = "err_code"; - String ERROR = "error"; - String ERRORS = "errors"; - String EXPIRES = "expires"; - String EXPIRES_IN = "expires_in"; - String EXTERNAL = "external"; - String FILTER = "filter"; - String FILTER_SUBJECT = "filter_subject"; - String FILTER_SUBJECTS = "filter_subjects"; - String FIRST_SEQ = "first_seq"; - String FIRST_TS = "first_ts"; - String FLOW_CONTROL = "flow_control"; - String GO = "go"; - String GROUP = "group"; - String HDRS = "hdrs"; - String HEADERS = "headers"; - String HEADERS_ONLY = "headers_only"; - String HOST = "host"; - String ID = "id"; - String IDLE_HEARTBEAT = "idle_heartbeat"; - String INACTIVE_THRESHOLD= "inactive_threshold"; - String INFLIGHT = "inflight"; - String INTERNAL = "internal"; - String JETSTREAM = "jetstream"; - String KEEP = "keep"; - String LAG = "lag"; - String LAME_DUCK_MODE = "ldm"; - String LAST_ACTIVE = "last_active"; - String LAST_BY_SUBJECT = "last_by_subj"; - String LAST_ERROR = "last_error"; - String LAST_SEQ = "last_seq"; - String LAST_TS = "last_ts"; - String LEADER = "leader"; - String LEVEL = "level"; - String LIMIT = "limit"; - String LIMITS = "limits"; - String LINK = "link"; - String LOST = "lost"; - String MAX_ACK_PENDING = "max_ack_pending"; - String MAX_AGE = "max_age"; - String MAX_BATCH = "max_batch"; - String MAX_BYTES = "max_bytes"; - String MAX_BYTES_REQUIRED= "max_bytes_required"; - String MAX_CONSUMERS = "max_consumers"; - String MAX_CHUNK_SIZE = "max_chunk_size"; - String MAX_DELIVER = "max_deliver"; - String MAX_EXPIRES = "max_expires"; - String MAX_MEMORY = "max_memory"; - String MAX_MSG_SIZE = "max_msg_size"; - String MAX_MSGS = "max_msgs"; - String MAX_MSGS_PER_SUB = "max_msgs_per_subject"; - String MAX_PAYLOAD = "max_payload"; - String MAX_STORAGE = "max_storage"; - String MAX_STREAMS = "max_streams"; - String MAX_WAITING = "max_waiting"; // this is correct! the meaning name is different than the field name - String MIN_PENDING = "min_pending"; - String MIN_ACK_PENDING = "min_ack_pending"; - String MEM_STORAGE = "mem_storage"; - String MEMORY = "memory"; - String MEMORY_MAX_STREAM_BYTES = "memory_max_stream_bytes"; - String MESSAGE = "message"; - String MESSAGES = "messages"; - String METADATA = "metadata"; - String MTIME = "mtime"; - String MIRROR = "mirror"; - String MIRROR_DIRECT = "mirror_direct"; - String MSGS = "msgs"; - String MULTI_LAST = "multi_last"; - String NAME = "name"; - String NEXT_BY_SUBJECT = "next_by_subj"; - String NO_ACK = "no_ack"; - String NO_ERASE = "no_erase"; - String NO_WAIT = "no_wait"; - String NONCE = "nonce"; - String NUID = "nuid"; - String NUM_ACK_PENDING = "num_ack_pending"; - String NUM_DELETED = "num_deleted"; - String NUM_ERRORS = "num_errors"; - String NUM_PENDING = "num_pending"; - String NUM_REDELIVERED = "num_redelivered"; - String NUM_REPLICAS = "num_replicas"; - String NUM_REQUESTS = "num_requests"; - String NUM_SUBJECTS = "num_subjects"; - String NUM_WAITING = "num_waiting"; - String OFFLINE = "offline"; - String OFFSET = "offset"; - String OPT_START_SEQ = "opt_start_seq"; - String OPT_START_TIME = "opt_start_time"; - String OPTIONS = "options"; - String PAUSED = "paused"; - String PAUSE_REMAINING = "pause_remaining"; - String PAUSE_UNTIL = "pause_until"; - String PLACEMENT = "placement"; - String PORT = "port"; - String PRIORITY_GROUPS = "priority_groups"; - String PRIORITY_POLICY = "priority_policy"; - String PROCESSING_TIME = "processing_time"; - String PROTO = "proto"; - String PURGED = "purged"; - String PUSH_BOUND = "push_bound"; - String QUEUE_GROUP = "queue_group"; - String RAISE_STATUS_WARNINGS = "raise_status_warnings"; - String RATE_LIMIT_BPS = "rate_limit_bps"; - String REPLAY_POLICY = "replay_policy"; - String REPLICA = "replica"; - String REPLICAS = "replicas"; - String REPUBLISH = "republish"; - String REQUEST = "request"; - String RESERVED_MEMORY = "reserved_memory"; - String RESERVED_STORAGE = "reserved_storage"; - String RESPONSE = "response"; - String RETENTION = "retention"; - String SAMPLE_FREQ = "sample_freq"; - String SCHEMA = "schema"; - String SEALED = "sealed"; - String SEQ = "seq"; - String SERVER_ID = "server_id"; - String SERVER_NAME = "server_name"; - String SIZE = "size"; - String SOURCE = "source"; - String SOURCES = "sources"; - String SRC = "src"; - String STARTED = "started"; - String START_TIME = "start_time"; - String STATE = "state"; - String STATS = "stats"; - String STORAGE = "storage"; - String STORAGE_MAX_STREAM_BYTES = "storage_max_stream_bytes"; - String STREAM_NAME = "stream_name"; - String STREAM_SEQ = "stream_seq"; - String STREAM = "stream"; - String STREAMS = "streams"; - String SUBJECT = "subject"; - String SUBJECT_DELETE_MARKER_TTL = "subject_delete_marker_ttl"; - String SUBJECT_TRANSFORM = "subject_transform"; - String SUBJECT_TRANSFORMS = "subject_transforms"; - String SUBJECTS = "subjects"; - String SUBJECTS_FILTER = "subjects_filter"; - String SUCCESS = "success"; - String TAGS = "tags"; - String TEMPLATE_OWNER = "template_owner"; - String THRESHOLD_PERCENT = "threshold_percent"; - String TIERS = "tiers"; - String TIME = "time"; - String TIMESTAMP = "ts"; - String TLS = "tls_required"; - String TLS_REQUIRED = TLS; - String TLS_AVAILABLE = "tls_available"; - String TOTAL = "total"; - String TYPE = "type"; - String UP_TO_SEQ = "up_to_seq"; - String UP_TO_TIME = "up_to_time"; - String VERSION = "version"; -} diff --git a/src/test/java/io/nats/client/support/DateTimeUtils.java b/src/test/java/io/nats/client/support/DateTimeUtils.java deleted file mode 100644 index 08c3fc8..0000000 --- a/src/test/java/io/nats/client/support/DateTimeUtils.java +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.nats.client.support; - -import java.time.*; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; - -/** - * Internal json parsing helpers. - */ -public abstract class DateTimeUtils { - private DateTimeUtils() {} /* ensures cannot be constructed */ - - public static final ZoneId ZONE_ID_GMT = ZoneId.of("GMT"); - public static final ZonedDateTime DEFAULT_TIME = ZonedDateTime.of(1, 1, 1, 0, 0, 0, 0, ZONE_ID_GMT); - public static final DateTimeFormatter RFC3339_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.nnnnnnnnn'Z'"); - private static final long NANO_FACTOR = 1_000_000_000; - - public static ZonedDateTime toGmt(ZonedDateTime zonedDateTime) { - return zonedDateTime.withZoneSameInstant(ZONE_ID_GMT); - } - - public static ZonedDateTime gmtNow() { - return ZonedDateTime.now().withZoneSameInstant(ZONE_ID_GMT); - } - - public static boolean equals(ZonedDateTime zdt1, ZonedDateTime zdt2) { - if (zdt1 == zdt2) return true; - if (zdt1 == null || zdt2 == null) return false; - return zdt1.withZoneSameInstant(ZONE_ID_GMT).equals(zdt2.withZoneSameInstant(ZONE_ID_GMT)); - } - - public static String toRfc3339(ZonedDateTime zonedDateTime) { - return RFC3339_FORMATTER.format(toGmt(zonedDateTime)); - } - - /** - * Parses a date time from the server. - * @param dateTime - date time from the server. - * @return a Zoned Date time. - */ - public static ZonedDateTime parseDateTime(String dateTime) { - return parseDateTime(dateTime, DEFAULT_TIME); - } - - public static ZonedDateTime parseDateTime(String dateTime, ZonedDateTime dflt) { - try { - return toGmt(ZonedDateTime.parse(dateTime)); - } - catch (DateTimeParseException s) { - return dflt; - } - } - - public static ZonedDateTime parseDateTimeThrowParseError(String dateTime) { - return toGmt(ZonedDateTime.parse(dateTime)); - } - - /** - * Parses a long timestamp with nano precission in epoch UTC to the system - * default time-zone date time - * - * @param timestampNanos String timestamp - * @return a local Zoned Date time. - */ - public static ZonedDateTime parseDateTimeNanos(String timestampNanos) { - return parseDateTimeNanos(timestampNanos, ZoneId.systemDefault()); - } - - /** - * Parses a long timestamp with nano precission in epoch UTC to a Zoned date - * time - * - * @param timestampNanos String timestamp - * @param zoneId ZoneId - * @return a Zoned Date time. - */ - public static ZonedDateTime parseDateTimeNanos(String timestampNanos, ZoneId zoneId) { - long ts = Long.parseLong(timestampNanos); - long seconds = ts / NANO_FACTOR; - long nanos = ts % NANO_FACTOR; - Instant utcInstant = Instant.ofEpochSecond(seconds, nanos); - OffsetDateTime utcOffsetDT = OffsetDateTime.ofInstant(utcInstant, ZoneOffset.UTC); - return utcOffsetDT.atZoneSameInstant(zoneId); - } - - public static ZonedDateTime fromNow(long millis) { - return ZonedDateTime.ofInstant(Instant.now().plusMillis(millis), ZONE_ID_GMT); - } - - public static ZonedDateTime fromNow(Duration dur) { - return ZonedDateTime.ofInstant(Instant.now().plusMillis(dur.toMillis()), ZONE_ID_GMT); - } -} diff --git a/src/test/java/io/synadia/json/Benchmark.java b/src/test/java/io/synadia/json/Benchmark.java deleted file mode 100644 index 2b9e2b1..0000000 --- a/src/test/java/io/synadia/json/Benchmark.java +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.synadia.json; - -import io.nats.client.api.ConsumerConfiguration; -import io.nats.client.api.StreamConfiguration; -import io.nats.client.api.StreamInfo; - -import java.text.NumberFormat; -import java.util.Locale; - -import static io.ResourceUtils.resourceAsBytes; - -public final class Benchmark { - static final String RESULTS_HEADER = " | Elapsed ms | Rounds | Rounds/Time | Time/Round |\n"; - static final String RESULTS_SEP = "-----------------------|-----------------|--------------|----------------|-------------------|\n"; - static final String RESULTS = "%-22s | %15s | %12s | %14s | %17s |\n"; - - static byte[] JSON_SI = resourceAsBytes("stream-info.json"); - static byte[] JSON_CC = resourceAsBytes("consumer-configuration.json"); - static JsonValue JV_SI = JsonParser.parseUnchecked(JSON_SI); - static JsonValue JV_CC = JsonParser.parseUnchecked(JSON_CC); - static StreamInfo SI = new StreamInfo(JV_SI); - static StreamConfiguration SC = SI.getConfiguration(); - static ConsumerConfiguration CC = ConsumerConfiguration.builder().jsonValue(JV_CC).build(); - - static long ROUNDS = 10_000_000; - static long REPORT_FREQUENCY = ROUNDS / 100; - static long NUM_DIFF_BENCHES = 7; - - public static void main(String[] args) { - long totalElapsed = 0; - long benchParseSiElapsed = 0; - long benchParseCcElapsed = 0; - long benchCreateSiElapsed = 0; - long benchCreateCcElapsed = 0; - long benchToJsonScElapsed = 0; - long benchToJsonCcElapsed = 0; - long benchUnitCoverage = 0; - for (int r = 1; r <= ROUNDS; r++) { - benchParseSiElapsed += benchParseSi(); - benchParseCcElapsed += benchParseCc(); - benchCreateSiElapsed += benchCreateSi(); - benchCreateCcElapsed += benchCreateCc(); - benchToJsonScElapsed += benchToJsonSc(); - benchToJsonCcElapsed += benchToJsonCc(); - benchUnitCoverage += BenchmarkUnitCoverage.unitCoverage(); - totalElapsed = benchParseSiElapsed + benchParseCcElapsed + benchCreateSiElapsed + benchCreateCcElapsed + benchToJsonScElapsed + benchToJsonCcElapsed + benchUnitCoverage; - if (r % REPORT_FREQUENCY == 0) { - printResults(r, - benchParseSiElapsed, benchParseCcElapsed, - benchCreateSiElapsed, benchCreateCcElapsed, - benchToJsonScElapsed, benchToJsonCcElapsed, - benchUnitCoverage, - totalElapsed); - } - } - - System.out.println("\n"); - printResults(ROUNDS, - benchParseSiElapsed, benchParseCcElapsed, - benchCreateSiElapsed, benchCreateCcElapsed, - benchToJsonScElapsed, benchToJsonCcElapsed, - benchUnitCoverage, - totalElapsed); - } - - private static void printResults(long rounds, long benchParseSiElapsed, long benchParseCcElapsed, long benchCreateSiElapsed, long benchCreateCcElapsed, long benchToJsonScElapsed, long benchToJsonCcElapsed, long benchUnitCoverage, long totalElapsed) { - System.out.println(); - System.out.printf(RESULTS_HEADER + RESULTS_SEP); - printResults("Parse Stream Info", benchParseSiElapsed, rounds); - printResults("Parse Consumer Config", benchParseCcElapsed, rounds); - printResults("Create Stream Info", benchCreateSiElapsed, rounds); - printResults("Create Consumer Config", benchCreateCcElapsed, rounds); - printResults("ToJson Stream Config", benchToJsonScElapsed, rounds); - printResults("ToJson Consumer Config", benchToJsonCcElapsed, rounds); - printResults("Unit Tests", benchUnitCoverage, rounds); - System.out.printf(RESULTS_SEP); - printResults("Total/Average", totalElapsed, rounds * NUM_DIFF_BENCHES); - } - - // ---------------------------------------------------------------------------------------------------- - // BENCHMARKS - // ---------------------------------------------------------------------------------------------------- - public static long benchParseSi() { - long start = System.nanoTime(); - JsonParser.parseUnchecked(JSON_SI); - return System.nanoTime() - start; - } - - public static long benchParseCc() { - long start = System.nanoTime(); - JsonParser.parseUnchecked(JSON_CC); - return System.nanoTime() - start; - } - - public static long benchCreateSi() { - long start = System.nanoTime(); - new StreamInfo(JV_SI); - return System.nanoTime() - start; - } - - public static long benchCreateCc() { - long start = System.nanoTime(); - ConsumerConfiguration.builder().jsonValue(JV_CC); - return System.nanoTime() - start; - } - - public static long benchToJsonSc() { - long start = System.nanoTime(); - SC.toJson(); - return System.nanoTime() - start; - } - - public static long benchToJsonCc() { - long start = System.nanoTime(); - CC.toJson(); - return System.nanoTime() - start; - } - - // ---------------------------------------------------------------------------------------------------- - // RESULT HELPERS - // ---------------------------------------------------------------------------------------------------- - private static void printResults(String label, Long elapsedNs, long rounds) { - float fElapsedNs = elapsedNs.floatValue(); - float elapsedMs = fElapsedNs / 1_000_000F; - String perTime = getOpsPerTime(fElapsedNs, elapsedMs, rounds); - String timePer = getTimePerOps(fElapsedNs, elapsedMs, rounds); - System.out.printf(RESULTS, label, format3(elapsedMs), format(rounds), perTime, timePer); - } - - private static String getOpsPerTime(float elapsedNs, float elapsedMs, long rounds) { - float nsPer = rounds / elapsedNs; - float msPer = rounds / elapsedMs; - return nsPer < 1F ? format3(msPer) + " r/ms" : format(nsPer) + " r/ns"; - } - - private static String getTimePerOps(float elapsedNs, float elapsedMs, long rounds) { - float nsPer = elapsedNs/ rounds; - float msPer = elapsedMs / rounds; - return nsPer < 1F ? format3(msPer) + " ms/r" : format(nsPer) + " ns/r"; - } - - public static String format(Number s) { - return NumberFormat.getNumberInstance(Locale.getDefault()).format(s); - } - - public static String format3(Number n) { - if (n.longValue() >= 1_000_000_000) { - return humanBytes(n.doubleValue()); - } - String f = format(n); - int at = f.indexOf('.'); - if (at == -1) { - return f; - } - if (at == 0) { - return f + "." + ZEROS.substring(0, 3); - } - return (f + ZEROS).substring(0, at + 3 + 1); - } - - public static String humanBytes(double bytes) { - if (bytes < HUMAN_BYTES_BASE) { - return String.format("%.2f b", bytes); - } - int exp = (int) (Math.log(bytes) / Math.log(HUMAN_BYTES_BASE)); - try { - return String.format("%.2f %s", bytes / Math.pow(HUMAN_BYTES_BASE, exp), HUMAN_BYTES_UNITS[exp]); - } - catch (Exception e) { - return String.format("%.2f b", bytes); - } - } - - private static final String ZEROS = "000000000"; - private static final long HUMAN_BYTES_BASE = 1024; - private static final String[] HUMAN_BYTES_UNITS = new String[] {"b", "kb", "mb", "gb", "tb", "pb", "eb"}; -} diff --git a/src/test/java/io/synadia/json/BenchmarkUnitCoverage.java b/src/test/java/io/synadia/json/BenchmarkUnitCoverage.java deleted file mode 100644 index c3069bf..0000000 --- a/src/test/java/io/synadia/json/BenchmarkUnitCoverage.java +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2025 Synadia Communications, Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package io.synadia.json; - -import io.ResourceUtils; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; - -import static io.synadia.json.JsonValue.*; -import static io.synadia.json.JsonValueUtils.*; - -@SuppressWarnings({"ResultOfMethodCallIgnored", "DataFlowIssue"}) -public final class BenchmarkUnitCoverage { - private static final String TEST_JSON = ResourceUtils.resourceAsString("test.json"); - private static final JsonValue TEST_JV = JsonParser.parseUnchecked(TEST_JSON); - private static final Duration DEFAULT_DURATION = Duration.ofSeconds(99); - - private static final String STRING = "string"; - private static final String INTEGER = "integer"; - private static final String LONG = "long"; - private static final String BIG_DECIMAL = "big_decimal"; - private static final String BOOL = "bool"; - private static final String DATE = "date"; - private static final String NANOS = "nanos"; - private static final String BASE_64_BASIC = "base_64_basic"; - private static final String BASE_64_URL = "base_64_url"; - private static final String MAP = "map"; - private static final String ARRAY = "array"; - private static final String MMAP = "mmap"; - private static final String SMAP = "smap"; - private static final String SLIST = "slist"; - private static final String MLIST = "mlist"; - private static final String ILIST = "ilist"; - private static final String LLIST = "llist"; - private static final String NLIST = "nlist"; - private static final String NOT_A_KEY = "not-a-key"; - - public static long unitCoverage() { - long start = System.nanoTime(); - - // testRead - read(TEST_JV, STRING, null, v -> v); - read(null, NOT_A_KEY, null, v -> v); - read(EMPTY_ARRAY, NOT_A_KEY, null, v -> v); - read(TRUE, NOT_A_KEY, null, v -> v); - read(FALSE, NOT_A_KEY, null, v -> v); - read(NULL, NOT_A_KEY, null, v -> v); - - // testReadStrings - readString(TEST_JV, STRING); - readBytes(TEST_JV, STRING); - readBytes(TEST_JV, STRING, StandardCharsets.UTF_8); - readString(TEST_JV, DATE); - readString(TEST_JV, BASE_64_BASIC); - readString(TEST_JV, BASE_64_URL); - readDate(TEST_JV, DATE); - readBase64Basic(TEST_JV, BASE_64_BASIC); - readBase64Url(TEST_JV, BASE_64_URL); - readBase64Basic(TEST_JV, INTEGER); - readBase64Url(TEST_JV, INTEGER); - readString(TEST_JV, INTEGER); - readString(TEST_JV, LONG); - readString(TEST_JV, BOOL); - readString(TEST_JV, MAP); - readString(TEST_JV, ARRAY); - readString(TEST_JV, NOT_A_KEY); - readString(TEST_JV, INTEGER, STRING); - readString(TEST_JV, LONG, STRING); - readString(TEST_JV, BOOL, STRING); - readString(TEST_JV, MAP, STRING); - readString(TEST_JV, ARRAY, STRING); - readString(TEST_JV, NOT_A_KEY, STRING); - readString(TEST_JV, NOT_A_KEY); - readString(TEST_JV, INTEGER); - readString(TEST_JV, STRING, STRING); - readString(TEST_JV, NOT_A_KEY, STRING); - readString(TEST_JV, INTEGER, STRING); - readString(null, NOT_A_KEY, STRING); - - // testReadInteger - readInteger(TEST_JV, INTEGER); - readInteger(TEST_JV, STRING); - readInteger(TEST_JV, BOOL); - readInteger(TEST_JV, MAP); - readInteger(TEST_JV, ARRAY); - readInteger(TEST_JV, NOT_A_KEY); - - readInteger(TEST_JV, STRING, 99); - readInteger(TEST_JV, BOOL, 99); - readInteger(TEST_JV, MAP, 99); - readInteger(TEST_JV, ARRAY, 99); - readInteger(TEST_JV, NOT_A_KEY, 99); - - readInteger(TEST_JV, INTEGER, 99); - readInteger(TEST_JV, STRING, 99); - readInteger(TEST_JV, BOOL, 99); - readInteger(TEST_JV, MAP, 99); - readInteger(TEST_JV, ARRAY, 99); - readInteger(TEST_JV, NOT_A_KEY, 99); - - // testReadLong - readLong(TEST_JV, INTEGER); - readLong(TEST_JV, LONG); - readLong(TEST_JV, STRING); - readLong(TEST_JV, BOOL); - readLong(TEST_JV, MAP); - readLong(TEST_JV, ARRAY); - readLong(TEST_JV, NOT_A_KEY); - readLong(TEST_JV, STRING, 99); - readLong(TEST_JV, BOOL, 99); - readLong(TEST_JV, MAP, 99); - readLong(TEST_JV, ARRAY, 99); - readLong(TEST_JV, NOT_A_KEY, 99); - readLong(TEST_JV, INTEGER, 99); - readLong(TEST_JV, LONG, 99); - readLong(TEST_JV, STRING, 99); - readLong(TEST_JV, BOOL, 99); - readLong(TEST_JV, MAP, 99); - readLong(TEST_JV, ARRAY, 99); - readLong(TEST_JV, NOT_A_KEY, 99); - - // testReadBoolean - readBoolean(TEST_JV, BOOL); - readBoolean(TEST_JV, STRING); - readBoolean(TEST_JV, INTEGER); - readBoolean(TEST_JV, LONG); - readBoolean(TEST_JV, MAP); - readBoolean(TEST_JV, ARRAY); - readBoolean(TEST_JV, NOT_A_KEY); - readBoolean(TEST_JV, BOOL, true); - readBoolean(TEST_JV, STRING, true); - readBoolean(TEST_JV, INTEGER, true); - readBoolean(TEST_JV, LONG, true); - readBoolean(TEST_JV, MAP, true); - readBoolean(TEST_JV, ARRAY, true); - readBoolean(TEST_JV, NOT_A_KEY, true); - readBoolean(TEST_JV, STRING, false); - readBoolean(TEST_JV, INTEGER, false); - readBoolean(TEST_JV, LONG, false); - readBoolean(TEST_JV, MAP, false); - readBoolean(TEST_JV, ARRAY, false); - readBoolean(TEST_JV, NOT_A_KEY, false); - - // testReadDate - readDate(TEST_JV, DATE); - readDate(TEST_JV, BOOL); - readDate(TEST_JV, MAP); - readDate(TEST_JV, ARRAY); - readDate(TEST_JV, NOT_A_KEY); - - // testReadNanosAsDuration - readNanosAsDuration(TEST_JV, NANOS); - readNanosAsDuration(TEST_JV, STRING); - readNanosAsDuration(TEST_JV, BOOL); - readNanosAsDuration(TEST_JV, MAP); - readNanosAsDuration(TEST_JV, ARRAY); - readNanosAsDuration(TEST_JV, NOT_A_KEY); - readNanosAsDuration(TEST_JV, NANOS, DEFAULT_DURATION); - readNanosAsDuration(TEST_JV, STRING, DEFAULT_DURATION); - readNanosAsDuration(TEST_JV, BOOL, DEFAULT_DURATION); - readNanosAsDuration(TEST_JV, MAP, DEFAULT_DURATION); - readNanosAsDuration(TEST_JV, ARRAY, DEFAULT_DURATION); - readNanosAsDuration(TEST_JV, NOT_A_KEY, DEFAULT_DURATION); - - // testObjectAndMaps - readMapObjectOrNull(TEST_JV, SMAP); - readMapMapOrNull(TEST_JV, SMAP); - readMapMapOrEmpty(TEST_JV, SMAP); - readMapObjectOrNull(TEST_JV, MMAP); - readMapObjectOrEmpty(TEST_JV, MMAP); - readMapMapOrNull(TEST_JV, MMAP); - readMapMapOrEmpty(TEST_JV, MMAP); - readStringMapOrNull(TEST_JV, MMAP); - readStringMapOrEmpty(TEST_JV, MMAP); - - // testArrays - listOfOrNull(null, jv -> jv); - listOfOrNull(readValue(TEST_JV, STRING), jv -> jv); - listOfOrNull(readValue(TEST_JV, SLIST), jv -> jv); - listOfOrEmpty(null, jv -> jv); - listOfOrEmpty(readValue(TEST_JV, STRING), jv -> jv); - listOfOrEmpty(readValue(TEST_JV, SLIST), jv -> jv); - readArrayOrNull(TEST_JV, STRING); - readArrayOrEmpty(TEST_JV, STRING); - readArrayOrNull(TEST_JV, SLIST); - readArrayOrEmpty(TEST_JV, SLIST); - readArrayOrNull(TEST_JV, MLIST); - readArrayOrEmpty(TEST_JV, MLIST); - readStringListOrNull(TEST_JV, STRING); - readStringListOrEmpty(TEST_JV, STRING); - readStringListOrNull(TEST_JV, SLIST); - readStringListOrEmpty(TEST_JV, SLIST); - readStringListOrNull(TEST_JV, MLIST); - readStringListOrEmpty(TEST_JV, MLIST); - readIntegerListOrNull(TEST_JV, STRING); - readIntegerListOrEmpty(TEST_JV, STRING); - readIntegerListOrNull(TEST_JV, SLIST); - readIntegerListOrEmpty(TEST_JV, SLIST); - readIntegerListOrNull(TEST_JV, LLIST); - readIntegerListOrEmpty(TEST_JV, LLIST); - readIntegerListOrNull(TEST_JV, MLIST); - readIntegerListOrEmpty(TEST_JV, MLIST); - readIntegerListOrNull(TEST_JV, ILIST); - readIntegerListOrEmpty(TEST_JV, ILIST); - readLongListOrNull(TEST_JV, STRING); - readLongListOrEmpty(TEST_JV, STRING); - readLongListOrNull(TEST_JV, SLIST); - readLongListOrEmpty(TEST_JV, SLIST); - readLongListOrNull(TEST_JV, MLIST); - readLongListOrEmpty(TEST_JV, MLIST); - readLongListOrNull(TEST_JV, ILIST); - readLongListOrEmpty(TEST_JV, ILIST); - readLongListOrNull(TEST_JV, LLIST); - readLongListOrEmpty(TEST_JV, LLIST); - readNanosAsDurationListOrNull(TEST_JV, STRING); - readNanosAsDurationListOrEmpty(TEST_JV, STRING); - readNanosAsDurationListOrNull(TEST_JV, NLIST); - readNanosAsDurationListOrEmpty(TEST_JV, NLIST); - - // testNotFoundOrWrongType() { - validateNotFoundOrWrongType(STRING, false, true, true, true, true, true, true); - validateNotFoundOrWrongType(INTEGER, true, true, false, false, true, true, true); - validateNotFoundOrWrongType(LONG, true, true, true, false, true, true, true); - validateNotFoundOrWrongType(BOOL, true, true, true, true, false, true, true); - validateNotFoundOrWrongType(DATE, false, false, true, true, false, true, true); - validateNotFoundOrWrongType(BASE_64_BASIC, false, true, true, true, true, true, true); - validateNotFoundOrWrongType(BIG_DECIMAL, true, true, true, true, true, true, true); - validateNotFoundOrWrongType(MAP, true, true, true, true, true, false, true); - validateNotFoundOrWrongType(ARRAY, true, true, true, true, true, true, false); - validateNotFoundOrWrongType(SMAP, true, true, true, true, true, false, true); - validateNotFoundOrWrongType(SLIST, true, true, true, true, true, true, false); - validateNotFoundOrWrongType(NOT_A_KEY, true, true, true, true, true, true, true); - - // testGetIntLong - JsonValue x = readValue(TEST_JV, STRING); - JsonValue i = new JsonValue(Integer.MAX_VALUE); - JsonValue li = new JsonValue((long)Integer.MAX_VALUE); - JsonValue lmax = new JsonValue(Long.MAX_VALUE); - JsonValue lmin = new JsonValue(Long.MIN_VALUE); - getInt(i, -1); - getInt(li, -1); - getInt(x, -1); - getInt(JsonValue.NULL, -1); - getInt(EMPTY_MAP, -1); - getInt(EMPTY_ARRAY, -1); - getInteger(i); - getInteger(li); - getInteger(x); - getInteger(lmax); - getInteger(lmin); - getInteger(JsonValue.NULL); - getInteger(EMPTY_MAP); - getInteger(EMPTY_ARRAY); - getLong(i); - getLong(li); - getLong(lmax); - getLong(lmin); - getLong(x); - getLong(JsonValue.NULL); - getLong(EMPTY_MAP); - getLong(EMPTY_ARRAY); - getLong(i, -1); - getLong(li, -1); - getLong(lmax, -1); - getLong(lmin, -1); - getLong(x, -1); - getLong(JsonValue.NULL, -1); - getLong(EMPTY_MAP, -1); - getLong(EMPTY_ARRAY, -1); - - return System.nanoTime() - start; - } - - private static void validateNotFoundOrWrongType( - String key, - boolean notString, - boolean notDate, - boolean notInteger, - boolean notLong, - boolean notBoolean, - boolean notMap, - boolean notArray) - { - JsonValue jv = readValue(TEST_JV, key); - if (notString) { - readBytes(TEST_JV, key); - readBytes(TEST_JV, key, StandardCharsets.UTF_8); - readDate(TEST_JV, key); - } - else { - readBytes(TEST_JV, key); - readBytes(TEST_JV, key, StandardCharsets.UTF_8); - } - if (notDate) { - if (jv == null || jv.string == null) { - readDate(TEST_JV, key); - } - } - if (notInteger) { - readInteger(TEST_JV, key); - readInteger(TEST_JV, key, -1); - } - if (notLong) { - readLong(TEST_JV, key); - readLong(TEST_JV, key, -1); - } - if (notBoolean) { - readBoolean(TEST_JV, key); - readBoolean(TEST_JV, key, false); - } - - if (notMap) { - readMapObjectOrNull(TEST_JV, key); - readMapObjectOrEmpty(TEST_JV, key); - readMapMapOrNull(TEST_JV, key); - readMapMapOrEmpty(TEST_JV, key); - readStringMapOrNull(TEST_JV, key); - readStringMapOrEmpty(TEST_JV, key); - } - if (notArray) { - readArrayOrNull(TEST_JV, key); - readArrayOrEmpty(TEST_JV, key); - } - } -} diff --git a/src/test/java/io/synadia/json/DateTimeUtilsTests.java b/src/test/java/io/synadia/json/DateTimeUtilsTests.java index 5cef21a..28b697b 100644 --- a/src/test/java/io/synadia/json/DateTimeUtilsTests.java +++ b/src/test/java/io/synadia/json/DateTimeUtilsTests.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/test/java/io/synadia/json/EncodingTests.java b/src/test/java/io/synadia/json/EncodingTests.java index ee59885..e5717fd 100644 --- a/src/test/java/io/synadia/json/EncodingTests.java +++ b/src/test/java/io/synadia/json/EncodingTests.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/test/java/io/synadia/json/JsonParsingTests.java b/src/test/java/io/synadia/json/JsonParsingTests.java index 39e3fd7..f76530a 100644 --- a/src/test/java/io/synadia/json/JsonParsingTests.java +++ b/src/test/java/io/synadia/json/JsonParsingTests.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: @@ -14,8 +17,6 @@ package io.synadia.json; import io.ResourceUtils; -import nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Test; @@ -317,24 +318,6 @@ public void testNullJsonValue() { assertEquals(JsonValue.NULL, new JsonValue((BigInteger) null)); } - @Test - public void equalsContract() { - Map map1 = new HashMap<>(); - map1.put("1", new JsonValue(1)); - Map map2 = new HashMap<>(); - map1.put("2", new JsonValue(2)); - List list3 = new ArrayList<>(); - list3.add(new JsonValue(3)); - List list4 = new ArrayList<>(); - list4.add(new JsonValue(4)); - EqualsVerifier.simple().forClass(JsonValue.class) - .withPrefabValues(Map.class, map1, map2) - .withPrefabValues(List.class, list3, list4) - .withIgnoredFields("object", "number", "mapOrder") - .suppress(Warning.BIGDECIMAL_EQUALITY) - .verify(); - } - private void validateParse(JsonValue expected, String json) throws JsonParseException { char[] ca = json.toCharArray(); byte[] ba = json.getBytes(); @@ -825,4 +808,55 @@ public void testCoverageAndEdges() { String json2 = jv.toJson(); assertEquals(json, json2); } + + @Test + public void testBasics() { + Map map1 = new HashMap<>(); + map1.put("key", new JsonValue("value2")); + Map map2 = new HashMap<>(); + map1.put("key", new JsonValue("value2")); + List list1 = new ArrayList<>(); + list1.add(new JsonValue("item1")); + List list2 = new ArrayList<>(); + list2.add(new JsonValue("item2")); + JsonValue[] array1 = {new JsonValue("item1"), new JsonValue(41)}; + JsonValue[] array2 = {new JsonValue("item2"), new JsonValue(42)}; + + JsonValue[] jvs = new JsonValue[]{ + new JsonValue("test string"), + new JsonValue("another string"), + new JsonValue(true), + new JsonValue(false), + new JsonValue(42), + new JsonValue(43), + new JsonValue(72L), + new JsonValue(73L), + new JsonValue(3.14), + new JsonValue(3.15), + new JsonValue(3.14f), + new JsonValue(3.15f), + new JsonValue(new BigDecimal("123.456")), + new JsonValue(new BigDecimal("321.456")), + new JsonValue(new BigInteger("123456789012345")), + new JsonValue(new BigInteger("134567890123456")), + new JsonValue(map1), + new JsonValue(map2), + new JsonValue(list1), + new JsonValue(list2), + new JsonValue(array1), + new JsonValue(array2) + }; + for (int i = 0; i < jvs.length; i++) { + for (int j = 0; j < jvs.length; j++) { + if (i == j) { + assertEquals(jvs[i], jvs[j]); + assertEquals(jvs[i].hashCode(), jvs[j].hashCode()); + } + else { + assertNotEquals(jvs[i], jvs[j]); + assertNotEquals(jvs[i].hashCode(), jvs[j].hashCode()); + } + } + } + } } diff --git a/src/test/java/io/synadia/json/JsonUnicodeParsingTest.java b/src/test/java/io/synadia/json/JsonUnicodeParsingTest.java index 821ff77..e91165d 100644 --- a/src/test/java/io/synadia/json/JsonUnicodeParsingTest.java +++ b/src/test/java/io/synadia/json/JsonUnicodeParsingTest.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/test/java/io/synadia/json/JsonValueUtilsTests.java b/src/test/java/io/synadia/json/JsonValueUtilsTests.java index 4870b0e..62eb837 100644 --- a/src/test/java/io/synadia/json/JsonValueUtilsTests.java +++ b/src/test/java/io/synadia/json/JsonValueUtilsTests.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2020-2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: diff --git a/src/test/java/io/synadia/json/JsonWriteUtilsTests.java b/src/test/java/io/synadia/json/JsonWriteUtilsTests.java index 955e079..67b8e5e 100644 --- a/src/test/java/io/synadia/json/JsonWriteUtilsTests.java +++ b/src/test/java/io/synadia/json/JsonWriteUtilsTests.java @@ -1,4 +1,7 @@ -// Copyright 2025 Synadia Communications, Inc. +// Copyright 2025 The NATS Authors +// +// Modifications Copyright 2025-2026 Synadia Communications, Inc. +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at: