From 7b1b22366a5429afe8034bbffc71a992fa966ecd Mon Sep 17 00:00:00 2001 From: Scott Fauerbach Date: Wed, 31 Jan 2024 10:26:46 -0500 Subject: [PATCH] Extract json, nkey and jwt utils to libraries (#1061) --- build.gradle | 5 +- .../jetstream/NatsJsMirrorSubUseCases.java | 2 +- src/main/java/io/nats/client/NKey.java | 741 -------------- .../io/nats/client/PullRequestOptions.java | 16 +- .../java/io/nats/client/PurgeOptions.java | 10 +- .../java/io/nats/client/api/ApiResponse.java | 10 +- .../client/api/ConsumerConfiguration.java | 62 +- .../client/api/ConsumerCreateRequest.java | 5 +- .../io/nats/client/api/ConsumerLimits.java | 8 +- .../java/io/nats/client/api/External.java | 2 +- .../nats/client/api/MessageDeleteRequest.java | 2 +- .../io/nats/client/api/MessageGetRequest.java | 2 +- .../java/io/nats/client/api/MessageInfo.java | 30 +- .../java/io/nats/client/api/ObjectInfo.java | 15 +- .../java/io/nats/client/api/ObjectLink.java | 8 +- .../java/io/nats/client/api/ObjectMeta.java | 15 +- .../io/nats/client/api/ObjectMetaOptions.java | 8 +- .../java/io/nats/client/api/Placement.java | 2 +- .../java/io/nats/client/api/Republish.java | 2 +- .../java/io/nats/client/api/SourceBase.java | 18 +- .../nats/client/api/StreamConfiguration.java | 14 +- .../io/nats/client/api/StreamInfoOptions.java | 2 +- .../io/nats/client/api/SubjectTransform.java | 2 +- .../io/nats/client/impl/StreamInfoReader.java | 2 +- .../io/nats/client/support/DateTimeUtils.java | 80 -- .../java/io/nats/client/support/Encoding.java | 265 ----- .../io/nats/client/support/HeadersUtils.java | 37 + .../client/support/JsonParseException.java | 17 - .../io/nats/client/support/JsonParser.java | 439 -------- .../nats/client/support/JsonSerializable.java | 28 - .../io/nats/client/support/JsonUtils.java | 315 ++---- .../io/nats/client/support/JsonValue.java | 275 ----- .../nats/client/support/JsonValueUtils.java | 375 ------- .../java/io/nats/client/support/JwtUtils.java | 171 ++-- .../io/nats/client/support/Validator.java | 30 + src/main/java/io/nats/service/Endpoint.java | 15 +- .../java/io/nats/service/EndpointStats.java | 26 +- .../java/io/nats/service/InfoResponse.java | 11 +- src/main/java/io/nats/service/Service.java | 13 +- .../java/io/nats/service/ServiceResponse.java | 18 +- .../java/io/nats/service/StatsResponse.java | 7 +- src/test/java/io/nats/client/AuthTests.java | 7 +- src/test/java/io/nats/client/NKeyTests.java | 600 ----------- .../client/api/AccountStatisticsTests.java | 3 +- .../nats/client/impl/JetStreamPullTests.java | 12 +- .../nats/client/impl/ListRequestsTests.java | 3 +- .../client/support/DateTimeUtilsTests.java | 86 -- .../io/nats/client/support/EncodingTests.java | 8 + .../nats/client/support/JsonParsingTests.java | 950 ------------------ .../io/nats/client/support/JwtUtilsTests.java | 3 + .../java/io/nats/service/ServiceTests.java | 15 +- 51 files changed, 413 insertions(+), 4379 deletions(-) delete mode 100644 src/main/java/io/nats/client/NKey.java delete mode 100644 src/main/java/io/nats/client/support/DateTimeUtils.java delete mode 100644 src/main/java/io/nats/client/support/Encoding.java create mode 100644 src/main/java/io/nats/client/support/HeadersUtils.java delete mode 100644 src/main/java/io/nats/client/support/JsonParseException.java delete mode 100644 src/main/java/io/nats/client/support/JsonParser.java delete mode 100644 src/main/java/io/nats/client/support/JsonSerializable.java delete mode 100644 src/main/java/io/nats/client/support/JsonValue.java delete mode 100644 src/main/java/io/nats/client/support/JsonValueUtils.java delete mode 100644 src/test/java/io/nats/client/NKeyTests.java delete mode 100644 src/test/java/io/nats/client/support/DateTimeUtilsTests.java delete mode 100644 src/test/java/io/nats/client/support/JsonParsingTests.java diff --git a/build.gradle b/build.gradle index ec2383178..e8a9a335e 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,10 @@ repositories { } dependencies { - implementation 'net.i2p.crypto:eddsa:0.3.0' + implementation 'io.nats:nkeys-java:1.5.2' + implementation 'io.nats:jnats-json:1.5.2' + implementation 'io.nats:jwt-java:1.5.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.0' testImplementation 'io.nats:jnats-server-runner:1.2.8' testImplementation 'nl.jqno.equalsverifier:equalsverifier:3.12.3' diff --git a/src/examples/java/io/nats/examples/jetstream/NatsJsMirrorSubUseCases.java b/src/examples/java/io/nats/examples/jetstream/NatsJsMirrorSubUseCases.java index d80648985..8d14cb8ac 100644 --- a/src/examples/java/io/nats/examples/jetstream/NatsJsMirrorSubUseCases.java +++ b/src/examples/java/io/nats/examples/jetstream/NatsJsMirrorSubUseCases.java @@ -24,7 +24,7 @@ import java.time.Duration; import java.util.List; -import static io.nats.client.support.JsonUtils.printFormatted; +import static io.nats.client.support.JsonWriteUtils.printFormatted; import static io.nats.examples.jetstream.NatsJsUtils.publish; /** diff --git a/src/main/java/io/nats/client/NKey.java b/src/main/java/io/nats/client/NKey.java deleted file mode 100644 index b2c93c254..000000000 --- a/src/main/java/io/nats/client/NKey.java +++ /dev/null @@ -1,741 +0,0 @@ -// Copyright 2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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; - -import net.i2p.crypto.eddsa.EdDSAEngine; -import net.i2p.crypto.eddsa.EdDSAPrivateKey; -import net.i2p.crypto.eddsa.EdDSAPublicKey; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.*; -import java.util.Arrays; - -import static io.nats.client.support.Encoding.base32Decode; -import static io.nats.client.support.Encoding.base32Encode; -import static io.nats.client.support.RandomUtils.PRAND; -import static io.nats.client.support.RandomUtils.SRAND; - -class DecodedSeed { - int prefix; - byte[] bytes; -} - -/** - *

- * The NATS ecosystem will be moving to Ed25519 keys for identity, - * authentication and authorization for entities such as Accounts, Users, - * Servers and Clusters. - *

- *

- * NKeys are based on the Ed25519 standard. This signing algorithm provides for - * the use of public and private keys to sign and verify data. NKeys is designed - * to formulate keys in a much friendlier fashion referencing work done in - * cryptocurrencies, specifically Stellar. Bitcoin and others use a form of - * Base58 (or Base58Check) to encode raw keys. Stellar utilizes a more - * traditional Base32 with a CRC16 and a version or prefix byte. NKeys utilizes - * a similar format with one or two prefix bytes. The base32 encoding of these - * prefixes will yield friendly human readable prefixes, e.g. 'N' = server, 'C' - * = cluster, 'O' = operator, 'A' = account, and 'U' = user to help developers - * and administrators quickly identify key types. - *

- *

- * Each NKey is generated from 32 bytes. These bytes are called the seed and are - * encoded, in the NKey world, into a string starting with the letter 'S', with - * a second character indicating the key’s type, e.g. "SU" is a seed for a u - * er key pair, "SA" is a seed for an account key pair. The seed can be used t - * create the Ed25519 public/private key pair and should be protected as a p - * ivate key. It is equivalent to the private key for a PGP key pair, or the m - * ster password for your password vault. - *

- *

- * Ed25519 uses the seed bytes to generate a key pair. The pair contains a - * private key, which can be used to sign data, and a public key which can be - * used to verify a signature. The public key can be distributed, and is not - * considered secret. - *

- *

- * The NKey libraries encode 32 byte public keys using Base32 and a CRC16 - * checksum plus a prefix based on the key type, e.g. U for a user key. - *

- *

- * The NKey libraries have support for exporting a 64 byte private key. This - * data is encoded into a string starting with the prefix ‘P’ for private. The - * 64 bytes in a private key consists of the 32 bytes of the seed followed by - * he 32 bytes of the public key. Essentially, the private key is redundant sin - * e you can get it back from the seed alone. The NATS team recommends sto - * ing the 32 byte seed and letting the NKey library regenerate anything els - * it needs for signing. - *

- *

- * The existence of both a seed and a private key can result in confusion. It is - * reasonable to simply think of Ed25519 as having a public key and a private - * seed, and ignore the longer private key concept. In fact, the NKey libraries - * generally expect you to create an NKey from either a public key, to use for - * verification, or a seed, to use for signing. - *

- *

- * The NATS system will utilize public NKeys for identification, the NATS system - * will never store or even have access to any private keys or seeds. - * Authentication will utilize a challenge-response mechanism based on a - * collection of random bytes called a nonce. - *

- *

- * Version note - 2.2.0 provided string arguments for seeds, this is not as safe - * as char arrays, so in 2.3.0 we have included a breaking change to char arrays. - * While this is not the proper version choice, NKeys aren't widely used, if at all yet, - * so we are making the change on a minor jump. - *

- */ -public class NKey { - - /** - * NKeys use a prefix byte to indicate their intended owner: 'N' = server, 'C' = - * cluster, 'A' = account, and 'U' = user. 'P' is used for private keys. The - * NKey class formalizes these into the enum NKey.Type. - */ - public enum Type { - /** A user NKey. */ - USER(PREFIX_BYTE_USER), - /** An account NKey. */ - ACCOUNT(PREFIX_BYTE_ACCOUNT), - /** A server NKey. */ - SERVER(PREFIX_BYTE_SERVER), - /** An operator NKey. */ - OPERATOR(PREFIX_BYTE_OPERATOR), - /** A cluster NKey. */ - CLUSTER(PREFIX_BYTE_CLUSTER), - /** A private NKey. */ - PRIVATE(PREFIX_BYTE_PRIVATE); - - private final int prefix; - - Type(int prefix) { - this.prefix = prefix; - } - - public static Type fromPrefix(int prefix) { - if (prefix == PREFIX_BYTE_ACCOUNT) { - return ACCOUNT; - } else if (prefix == PREFIX_BYTE_SERVER) { - return SERVER; - } else if (prefix == PREFIX_BYTE_USER) { - return USER; - } else if (prefix == PREFIX_BYTE_CLUSTER) { - return CLUSTER; - } else if (prefix == PREFIX_BYTE_PRIVATE) { - return ACCOUNT; - } else if (prefix == PREFIX_BYTE_OPERATOR) { - return OPERATOR; - } - - throw new IllegalArgumentException("Unknown prefix"); - } - } - - // PrefixByteSeed is the prefix byte used for encoded NATS Seeds - private static final int PREFIX_BYTE_SEED = 18 << 3; // Base32-encodes to 'S...' - - // PrefixBytePrivate is the prefix byte used for encoded NATS Private keys - static final int PREFIX_BYTE_PRIVATE = 15 << 3; // Base32-encodes to 'P...' - - // PrefixByteServer is the prefix byte used for encoded NATS Servers - static final int PREFIX_BYTE_SERVER = 13 << 3; // Base32-encodes to 'N...' - - // PrefixByteCluster is the prefix byte used for encoded NATS Clusters - static final int PREFIX_BYTE_CLUSTER = 2 << 3; // Base32-encodes to 'C...' - - // PrefixByteAccount is the prefix byte used for encoded NATS Accounts - static final int PREFIX_BYTE_ACCOUNT = 0; // Base32-encodes to 'A...' - - // PrefixByteUser is the prefix byte used for encoded NATS Users - static final int PREFIX_BYTE_USER = 20 << 3; // Base32-encodes to 'U...' - - // PrefixByteOperator is the prefix byte used for encoded NATS Operators - static final int PREFIX_BYTE_OPERATOR = 14 << 3; // Base32-encodes to 'O...' - - private static final int ED25519_PUBLIC_KEYSIZE = 32; - private static final int ED25519_PRIVATE_KEYSIZE = 64; - private static final int ED25519_SEED_SIZE = 32; - private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519); - - // XModem CRC based on the go version of NKeys - private final static int[] crc16table = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, - 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, - 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, - 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, - 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, - 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, - 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, - 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, - 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, - 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, - 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, - 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, - 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, - 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, - 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, - 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, - 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, - 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; - - static int crc16(byte[] bytes) { - int crc = 0; - - for (byte b : bytes) { - crc = ((crc << 8) & 0xffff) ^ crc16table[((crc >> 8) ^ (b & 0xFF)) & 0x00FF]; - } - - return crc; - } - - - private static boolean checkValidPublicPrefixByte(int prefix) { - switch (prefix) { - case PREFIX_BYTE_SERVER: - case PREFIX_BYTE_CLUSTER: - case PREFIX_BYTE_OPERATOR: - case PREFIX_BYTE_ACCOUNT: - case PREFIX_BYTE_USER: - return true; - } - return false; - } - - static char[] removePaddingAndClear(char[] withPad) { - int i; - - for (i=withPad.length-1;i>=0;i--) { - if (withPad[i] != '=') { - break; - } - } - char[] withoutPad = new char[i+1]; - System.arraycopy(withPad, 0, withoutPad, 0, withoutPad.length); - - for (int j=0;j> 5); - int b2 = (type.prefix & 31) << 3; // 31 = 00011111 - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - bytes.write(b1); - bytes.write(b2); - bytes.write(src); - - int crc = crc16(bytes.toByteArray()); - byte[] littleEndian = ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort((short) crc).array(); - - bytes.write(littleEndian); - - char[] withPad = base32Encode(bytes.toByteArray()); - return removePaddingAndClear(withPad); - } - - static byte[] decode(char[] src) { - byte[] raw = base32Decode(src); - - if (raw == null || raw.length < 4) { - throw new IllegalArgumentException("Invalid encoding for source string"); - } - - byte[] crcBytes = Arrays.copyOfRange(raw, raw.length - 2, raw.length); - byte[] dataBytes = Arrays.copyOfRange(raw, 0, raw.length - 2); - - int crc = ByteBuffer.wrap(crcBytes).order(ByteOrder.LITTLE_ENDIAN).getShort() & 0xFFFF; - int actual = crc16(dataBytes); - - if (actual != crc) { - throw new IllegalArgumentException("CRC is invalid"); - } - - return dataBytes; - } - - static byte[] decode(Type expectedType, char[] src, boolean safe) { - byte[] raw = decode(src); - byte[] dataBytes = Arrays.copyOfRange(raw, 1, raw.length); - Type type = NKey.Type.fromPrefix(raw[0] & 0xFF); - - if (type != expectedType) { - if (safe) { - return null; - } - throw new IllegalArgumentException("Unexpected type"); - } - - return dataBytes; - } - - static DecodedSeed decodeSeed(char[] seed) { - byte[] raw = decode(seed); - - // Need to do the reverse here to get back to internal representation. - int b1 = raw[0] & 248; // 248 = 11111000 - int b2 = (raw[0] & 7) << 5 | ((raw[1] & 248) >> 3); // 7 = 00000111 - - if (b1 != PREFIX_BYTE_SEED) { - throw new IllegalArgumentException("Invalid encoding"); - } - - if (!checkValidPublicPrefixByte(b2)) { - throw new IllegalArgumentException("Invalid encoded prefix byte"); - } - - byte[] dataBytes = Arrays.copyOfRange(raw, 2, raw.length); - DecodedSeed retVal = new DecodedSeed(); - retVal.prefix = b2; - retVal.bytes = dataBytes; - return retVal; - } - - private static NKey createPair(Type type, SecureRandom random) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - if (random == null) { - random = SRAND; - } - - byte[] seed = new byte[NKey.ed25519.getCurve().getField().getb() / 8]; - random.nextBytes(seed); - - return createPair(type, seed); - } - - private static NKey createPair(Type type, byte[] seed) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, NKey.ed25519); - EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec); - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKey.getA(), NKey.ed25519); - EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec); - byte[] pubBytes = pubKey.getAbyte(); - - byte[] bytes = new byte[pubBytes.length + seed.length]; - System.arraycopy(seed, 0, bytes, 0, seed.length); - System.arraycopy(pubBytes, 0, bytes, seed.length, pubBytes.length); - - char[] encoded = encodeSeed(type, bytes); - return new NKey(type, null, encoded); - } - - /** - * Create an Account NKey from the provided random number generator. - * - * If no random is provided, SecureRandom() will be used to create one. - * - * The new NKey contains the private seed, which should be saved in a secure location. - * - * @param random A secure random provider - * @return the new Nkey - * @throws IOException if the seed cannot be encoded to a string - * @throws NoSuchProviderException if the default secure random cannot be created - * @throws NoSuchAlgorithmException if the default secure random cannot be created - */ - public static NKey createAccount(SecureRandom random) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - return createPair(Type.ACCOUNT, random); - } - - /** - * Create an Cluster NKey from the provided random number generator. - * - * If no random is provided, SecureRandom() will be used to create one. - * - * The new NKey contains the private seed, which should be saved in a secure location. - * - * @param random A secure random provider - * @return the new Nkey - * @throws IOException if the seed cannot be encoded to a string - * @throws NoSuchProviderException if the default secure random cannot be created - * @throws NoSuchAlgorithmException if the default secure random cannot be created - */ - public static NKey createCluster(SecureRandom random) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - return createPair(Type.CLUSTER, random); - } - - /** - * Create an Operator NKey from the provided random number generator. - * - * If no random is provided, SecureRandom() will be used to create one. - * - * The new NKey contains the private seed, which should be saved in a secure location. - * - * @param random A secure random provider - * @return the new Nkey - * @throws IOException if the seed cannot be encoded to a string - * @throws NoSuchProviderException if the default secure random cannot be created - * @throws NoSuchAlgorithmException if the default secure random cannot be created - */ - public static NKey createOperator(SecureRandom random) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - return createPair(Type.OPERATOR, random); - } - - /** - * Create a Server NKey from the provided random number generator. - * - * If no random is provided, SecureRandom() will be used to create one. - * - * The new NKey contains the private seed, which should be saved in a secure location. - * - * @param random A secure random provider - * @return the new Nkey - * @throws IOException if the seed cannot be encoded to a string - * @throws NoSuchProviderException if the default secure random cannot be created - * @throws NoSuchAlgorithmException if the default secure random cannot be created - */ - public static NKey createServer(SecureRandom random) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - return createPair(Type.SERVER, random); - } - - /** - * Create a User NKey from the provided random number generator. - * - * If no random is provided, SecureRandom() will be used to create one. - * - * The new NKey contains the private seed, which should be saved in a secure location. - * - * @param random A secure random provider - * @return the new Nkey - * @throws IOException if the seed cannot be encoded to a string - * @throws NoSuchProviderException if the default secure random cannot be created - * @throws NoSuchAlgorithmException if the default secure random cannot be created - */ - public static NKey createUser(SecureRandom random) - throws IOException, NoSuchProviderException, NoSuchAlgorithmException { - return createPair(Type.USER, random); - } - - /** - * Create an NKey object from the encoded public key. This NKey can be used for verification but not for signing. - * - * @param publicKey the string encoded public key - * @return the new Nkey - */ - public static NKey fromPublicKey(char[] publicKey) { - byte[] raw = decode(publicKey); - int prefix = raw[0] & 0xFF; - - if (!checkValidPublicPrefixByte(prefix)) { - throw new IllegalArgumentException("Not a valid public NKey"); - } - - Type type = NKey.Type.fromPrefix(prefix); - return new NKey(type, publicKey, null); - } - - /** - * Creates an NKey object from a string encoded seed. This NKey can be used to sign or verify. - * - * @param seed the string encoded seed, see {@link NKey#getSeed() getSeed()} - * @return the Nkey - */ - public static NKey fromSeed(char[] seed) { - DecodedSeed decoded = decodeSeed(seed); // Should throw on bad seed - - if (decoded.bytes.length == ED25519_PRIVATE_KEYSIZE) { - return new NKey(Type.fromPrefix(decoded.prefix), null, seed); - } else { - try { - return createPair(Type.fromPrefix(decoded.prefix), decoded.bytes); - } catch (Exception e) { - throw new IllegalArgumentException("Bad seed value", e); - } - } - } - - /** - * @param src the encoded public key - * @return true if the public key is an account public key - */ - public static boolean isValidPublicAccountKey(char[] src) { - return decode(Type.ACCOUNT, src, true) != null; - } - - /** - * @param src the encoded public key - * @return true if the public key is a cluster public key - */ - public static boolean isValidPublicClusterKey(char[] src) { - return decode(Type.CLUSTER, src, true) != null; - } - - /** - * @param src the encoded public key - * @return true if the public key is an operator public key - */ - public static boolean isValidPublicOperatorKey(char[] src) { - return decode(Type.OPERATOR, src, true) != null; - } - - /** - * @param src the encoded public key - * @return true if the public key is a server public key - */ - public static boolean isValidPublicServerKey(char[] src) { - return decode(Type.SERVER, src, true) != null; - } - - /** - * @param src the encoded public key - * @return true if the public key is a user public key - */ - public static boolean isValidPublicUserKey(char[] src) { - return decode(Type.USER, src, true) != null; - } - - /** - * The seed or private key per the Ed25519 spec, encoded with encodeSeed. - */ - private char[] privateKeyAsSeed; - - /** - * The public key, maybe null. Used for public only NKeys. - */ - private char[] publicKey; - - private Type type; - - private NKey(Type t, char[] publicKey, char[] privateKey) { - this.type = t; - this.privateKeyAsSeed = privateKey; - this.publicKey = publicKey; - } - - /** - * Clear the seed and public key char arrays by filling them - * with random bytes then zero-ing them out. - * - * The nkey is unusable after this operation. - */ - public void clear() { - if (privateKeyAsSeed != null) { - for (int i=0; i< privateKeyAsSeed.length ; i++) { - privateKeyAsSeed[i] = (char)(PRAND.nextInt(26) + 'a'); - } - Arrays.fill(privateKeyAsSeed, '\0'); - } - if (publicKey != null) { - for (int i=0; i< publicKey.length ; i++) { - publicKey[i] = (char)(PRAND.nextInt(26) + 'a'); - } - Arrays.fill(publicKey, '\0'); - } - } - - /** - * @return the string encoded seed for this NKey - */ - public char[] getSeed() { - if (privateKeyAsSeed == null) { - throw new IllegalStateException("Public-only NKey"); - } - DecodedSeed decoded = decodeSeed(privateKeyAsSeed); - byte[] seedBytes = new byte[ED25519_SEED_SIZE]; - System.arraycopy(decoded.bytes, 0, seedBytes, 0, seedBytes.length); - try { - return encodeSeed(Type.fromPrefix(decoded.prefix), seedBytes); - } catch (Exception e) { - throw new IllegalStateException("Unable to create seed.", e); - } - } - - /** - * @return the encoded public key for this NKey - * - * @throws GeneralSecurityException if there is an encryption problem - * @throws IOException if there is a problem encoding the public - * key - */ - public char[] getPublicKey() throws GeneralSecurityException, IOException { - if (publicKey != null) { - return publicKey; - } - - KeyPair keys = getKeyPair(); - EdDSAPublicKey pubKey = (EdDSAPublicKey) keys.getPublic(); - byte[] pubBytes = pubKey.getAbyte(); - - return encode(this.type, pubBytes); - } - - /** - * @return the encoded private key for this NKey - * - * @throws GeneralSecurityException if there is an encryption problem - * @throws IOException if there is a problem encoding the key - */ - public char[] getPrivateKey() throws GeneralSecurityException, IOException { - if (privateKeyAsSeed == null) { - throw new IllegalStateException("Public-only NKey"); - } - - DecodedSeed decoded = decodeSeed(privateKeyAsSeed); - return encode(Type.PRIVATE, decoded.bytes); - } - - /** - * @return A Java security keypair that represents this NKey in Java security - * form. - * - * @throws GeneralSecurityException if there is an encryption problem - * @throws IOException if there is a problem encoding or decoding - */ - public KeyPair getKeyPair() throws GeneralSecurityException, IOException { - if (privateKeyAsSeed == null) { - throw new IllegalStateException("Public-only NKey"); - } - - DecodedSeed decoded = decodeSeed(privateKeyAsSeed); - byte[] seedBytes = new byte[ED25519_SEED_SIZE]; - byte[] pubBytes = new byte[ED25519_PUBLIC_KEYSIZE]; - - System.arraycopy(decoded.bytes, 0, seedBytes, 0, seedBytes.length); - System.arraycopy(decoded.bytes, seedBytes.length, pubBytes, 0, pubBytes.length); - - EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seedBytes, NKey.ed25519); - EdDSAPrivateKey privKey = new EdDSAPrivateKey(privKeySpec); - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(pubBytes, NKey.ed25519); - EdDSAPublicKey pubKey = new EdDSAPublicKey(pubKeySpec); - - return new KeyPair(pubKey, privKey); - } - - /** - * @return the Type of this NKey - */ - public Type getType() { - return type; - } - - /** - * Sign aribitrary binary input. - * - * @param input the bytes to sign - * @return the signature for the input from the NKey - * - * @throws GeneralSecurityException if there is an encryption problem - * @throws IOException if there is a problem reading the data - */ - public byte[] sign(byte[] input) throws GeneralSecurityException, IOException { - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(NKey.ed25519.getHashAlgorithm())); - PrivateKey sKey = getKeyPair().getPrivate(); - - sgr.initSign(sKey); - sgr.update(input); - - return sgr.sign(); - } - - /** - * Verify a signature. - * - * @param input the bytes that were signed - * @param signature the bytes for the signature - * @return true if the signature matches this keys signature for the input. - * - * @throws GeneralSecurityException if there is an encryption problem - * @throws IOException if there is a problem reading the data - */ - public boolean verify(byte[] input, byte[] signature) throws GeneralSecurityException, IOException { - Signature sgr = new EdDSAEngine(MessageDigest.getInstance(NKey.ed25519.getHashAlgorithm())); - PublicKey sKey = null; - - if (privateKeyAsSeed != null) { - sKey = getKeyPair().getPublic(); - } else { - char[] encodedPublicKey = getPublicKey(); - byte[] decodedPublicKey = decode(this.type, encodedPublicKey, false); - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(decodedPublicKey, NKey.ed25519); - sKey = new EdDSAPublicKey(pubKeySpec); - } - - sgr.initVerify(sKey); - sgr.update(input); - - return sgr.verify(signature); - } - - @Override - public boolean equals(Object o) { - if (o == this) - return true; - if (!(o instanceof NKey)) { - return false; - } - - NKey otherNKey = (NKey) o; - - if (this.type != otherNKey.type) { - return false; - } - - if (this.privateKeyAsSeed == null) { - return Arrays.equals(this.publicKey, otherNKey.publicKey); - } - - return Arrays.equals(this.privateKeyAsSeed, otherNKey.privateKeyAsSeed); - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + this.type.prefix; - - if (this.privateKeyAsSeed == null) { - result = 31 * result + Arrays.hashCode(this.publicKey); - } else { - result = 31 * result + Arrays.hashCode(this.privateKeyAsSeed); - } - return result; - } - -} \ No newline at end of file diff --git a/src/main/java/io/nats/client/PullRequestOptions.java b/src/main/java/io/nats/client/PullRequestOptions.java index e1feb91f0..2c97e7328 100644 --- a/src/main/java/io/nats/client/PullRequestOptions.java +++ b/src/main/java/io/nats/client/PullRequestOptions.java @@ -14,11 +14,11 @@ package io.nats.client; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import java.time.Duration; import static io.nats.client.support.ApiConstants.*; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.validateGtZero; /** @@ -42,13 +42,13 @@ public PullRequestOptions(Builder b) { @Override public String toJson() { - StringBuilder sb = JsonUtils.beginJson(); - JsonUtils.addField(sb, BATCH, batchSize); - JsonUtils.addField(sb, MAX_BYTES, maxBytes); - JsonUtils.addFldWhenTrue(sb, NO_WAIT, noWait); - JsonUtils.addFieldAsNanos(sb, EXPIRES, expiresIn); - JsonUtils.addFieldAsNanos(sb, IDLE_HEARTBEAT, idleHeartbeat); - return JsonUtils.endJson(sb).toString(); + StringBuilder sb = beginJson(); + addField(sb, BATCH, batchSize); + addField(sb, MAX_BYTES, maxBytes); + addFldWhenTrue(sb, NO_WAIT, noWait); + addFieldAsNanos(sb, EXPIRES, expiresIn); + addFieldAsNanos(sb, IDLE_HEARTBEAT, idleHeartbeat); + return endJson(sb).toString(); } /** diff --git a/src/main/java/io/nats/client/PurgeOptions.java b/src/main/java/io/nats/client/PurgeOptions.java index ac9138cbc..9f265d633 100644 --- a/src/main/java/io/nats/client/PurgeOptions.java +++ b/src/main/java/io/nats/client/PurgeOptions.java @@ -14,11 +14,9 @@ package io.nats.client; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.validateSubject; /** @@ -39,9 +37,9 @@ private PurgeOptions(String subject, long seq, long keep) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, FILTER, subject); - JsonUtils.addField(sb, SEQ, seq); - JsonUtils.addField(sb, KEEP, keep); + addField(sb, FILTER, subject); + addField(sb, SEQ, seq); + addField(sb, KEEP, keep); return endJson(sb).toString(); } diff --git a/src/main/java/io/nats/client/api/ApiResponse.java b/src/main/java/io/nats/client/api/ApiResponse.java index 2dbe3a348..de6b2f36f 100644 --- a/src/main/java/io/nats/client/api/ApiResponse.java +++ b/src/main/java/io/nats/client/api/ApiResponse.java @@ -15,12 +15,16 @@ import io.nats.client.JetStreamApiException; import io.nats.client.Message; -import io.nats.client.support.*; +import io.nats.client.support.JsonParseException; +import io.nats.client.support.JsonParser; +import io.nats.client.support.JsonValue; +import io.nats.client.support.JsonValueUtils; import static io.nats.client.support.ApiConstants.ERROR; import static io.nats.client.support.ApiConstants.TYPE; import static io.nats.client.support.JsonValueUtils.readString; import static io.nats.client.support.JsonValueUtils.readValue; +import static io.nats.client.support.JsonWriteUtils.toKey; public abstract class ApiResponse { @@ -118,8 +122,6 @@ public Error getErrorObject() { @Override public String toString() { - return jv == null - ? JsonUtils.toKey(getClass()) + "\":null" - : jv.toString(getClass()); + return jv == null ? toKey(getClass()) + "\":null" : jv.toString(getClass()); } } diff --git a/src/main/java/io/nats/client/api/ConsumerConfiguration.java b/src/main/java/io/nats/client/api/ConsumerConfiguration.java index e3c093e7e..e80ceb02d 100644 --- a/src/main/java/io/nats/client/api/ConsumerConfiguration.java +++ b/src/main/java/io/nats/client/api/ConsumerConfiguration.java @@ -17,7 +17,6 @@ import io.nats.client.PushSubscribeOptions; import io.nats.client.support.ApiConstants; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import java.time.Duration; @@ -25,9 +24,8 @@ import java.util.*; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.NatsJetStreamClientError.JsConsumerNameDurableMismatch; import static io.nats.client.support.Validator.*; @@ -204,39 +202,39 @@ protected ConsumerConfiguration(Builder b) */ public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, DESCRIPTION, description); - JsonUtils.addField(sb, DURABLE_NAME, durable); - JsonUtils.addField(sb, NAME, name); - JsonUtils.addField(sb, DELIVER_SUBJECT, deliverSubject); - JsonUtils.addField(sb, DELIVER_GROUP, deliverGroup); - JsonUtils.addField(sb, DELIVER_POLICY, GetOrDefault(deliverPolicy).toString()); - JsonUtils.addFieldWhenGtZero(sb, OPT_START_SEQ, startSeq); - JsonUtils.addField(sb, OPT_START_TIME, startTime); - JsonUtils.addField(sb, ACK_POLICY, GetOrDefault(ackPolicy).toString()); - JsonUtils.addFieldAsNanos(sb, ACK_WAIT, ackWait); - JsonUtils.addFieldWhenGtZero(sb, MAX_DELIVER, maxDeliver); - JsonUtils.addField(sb, MAX_ACK_PENDING, maxAckPending); - JsonUtils.addField(sb, REPLAY_POLICY, GetOrDefault(replayPolicy).toString()); - JsonUtils.addField(sb, SAMPLE_FREQ, sampleFrequency); - JsonUtils.addFieldWhenGtZero(sb, RATE_LIMIT_BPS, rateLimit); - JsonUtils.addFieldAsNanos(sb, IDLE_HEARTBEAT, idleHeartbeat); - JsonUtils.addFldWhenTrue(sb, FLOW_CONTROL, flowControl); - JsonUtils.addField(sb, ApiConstants.MAX_WAITING, maxPullWaiting); - JsonUtils.addFldWhenTrue(sb, HEADERS_ONLY, headersOnly); - JsonUtils.addField(sb, MAX_BATCH, maxBatch); - JsonUtils.addField(sb, MAX_BYTES, maxBytes); - JsonUtils.addFieldAsNanos(sb, MAX_EXPIRES, maxExpires); - JsonUtils.addFieldAsNanos(sb, INACTIVE_THRESHOLD, inactiveThreshold); - JsonUtils.addDurations(sb, BACKOFF, backoff); - JsonUtils.addField(sb, NUM_REPLICAS, numReplicas); - JsonUtils.addField(sb, MEM_STORAGE, memStorage); - JsonUtils.addField(sb, METADATA, metadata); + 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); + addFldWhenTrue(sb, FLOW_CONTROL, flowControl); + addField(sb, ApiConstants.MAX_WAITING, maxPullWaiting); + addFldWhenTrue(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, MEM_STORAGE, memStorage); + addField(sb, METADATA, metadata); if (filterSubjects != null) { if (filterSubjects.size() > 1) { - JsonUtils.addStrings(sb, FILTER_SUBJECTS, filterSubjects); + addStrings(sb, FILTER_SUBJECTS, filterSubjects); } else if (filterSubjects.size() == 1) { - JsonUtils.addField(sb, FILTER_SUBJECT, filterSubjects.get(0)); + addField(sb, FILTER_SUBJECT, filterSubjects.get(0)); } } return endJson(sb).toString(); diff --git a/src/main/java/io/nats/client/api/ConsumerCreateRequest.java b/src/main/java/io/nats/client/api/ConsumerCreateRequest.java index 3b3bb1d88..80e339ac7 100644 --- a/src/main/java/io/nats/client/api/ConsumerCreateRequest.java +++ b/src/main/java/io/nats/client/api/ConsumerCreateRequest.java @@ -14,11 +14,10 @@ package io.nats.client.api; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import static io.nats.client.support.ApiConstants.CONFIG; import static io.nats.client.support.ApiConstants.STREAM_NAME; -import static io.nats.client.support.JsonUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; /** * Object used to make a request to create a consumer. Used Internally @@ -45,7 +44,7 @@ public String toJson() { StringBuilder sb = beginJson(); addField(sb, STREAM_NAME, streamName); - JsonUtils.addField(sb, CONFIG, config); + addField(sb, CONFIG, config); return endJson(sb).toString(); } diff --git a/src/main/java/io/nats/client/api/ConsumerLimits.java b/src/main/java/io/nats/client/api/ConsumerLimits.java index c866fbd7c..188191656 100644 --- a/src/main/java/io/nats/client/api/ConsumerLimits.java +++ b/src/main/java/io/nats/client/api/ConsumerLimits.java @@ -14,7 +14,6 @@ package io.nats.client.api; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import java.time.Duration; @@ -22,10 +21,9 @@ import static io.nats.client.api.ConsumerConfiguration.*; import static io.nats.client.support.ApiConstants.INACTIVE_THRESHOLD; import static io.nats.client.support.ApiConstants.MAX_ACK_PENDING; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.readInteger; import static io.nats.client.support.JsonValueUtils.readNanos; +import static io.nats.client.support.JsonWriteUtils.*; /** * ConsumerLimits @@ -66,8 +64,8 @@ public long getMaxAckPending() { public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addFieldAsNanos(sb, INACTIVE_THRESHOLD, inactiveThreshold); - JsonUtils.addField(sb, MAX_ACK_PENDING, maxAckPending); + addFieldAsNanos(sb, INACTIVE_THRESHOLD, inactiveThreshold); + addField(sb, MAX_ACK_PENDING, maxAckPending); return endJson(sb).toString(); } diff --git a/src/main/java/io/nats/client/api/External.java b/src/main/java/io/nats/client/api/External.java index 178150c1a..446052010 100644 --- a/src/main/java/io/nats/client/api/External.java +++ b/src/main/java/io/nats/client/api/External.java @@ -19,7 +19,7 @@ import static io.nats.client.support.ApiConstants.API; import static io.nats.client.support.ApiConstants.DELIVER; -import static io.nats.client.support.JsonUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; /** * External configuration referencing a stream source in another account diff --git a/src/main/java/io/nats/client/api/MessageDeleteRequest.java b/src/main/java/io/nats/client/api/MessageDeleteRequest.java index 02ffbe34c..39c569968 100644 --- a/src/main/java/io/nats/client/api/MessageDeleteRequest.java +++ b/src/main/java/io/nats/client/api/MessageDeleteRequest.java @@ -17,7 +17,7 @@ import static io.nats.client.support.ApiConstants.NO_ERASE; import static io.nats.client.support.ApiConstants.SEQ; -import static io.nats.client.support.JsonUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; /** * Object used to make a request for message delete requests. diff --git a/src/main/java/io/nats/client/api/MessageGetRequest.java b/src/main/java/io/nats/client/api/MessageGetRequest.java index 17a2caad3..7be668cd6 100644 --- a/src/main/java/io/nats/client/api/MessageGetRequest.java +++ b/src/main/java/io/nats/client/api/MessageGetRequest.java @@ -16,7 +16,7 @@ import io.nats.client.support.JsonSerializable; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; /** * Object used to make a request for special message get requests. diff --git a/src/main/java/io/nats/client/api/MessageInfo.java b/src/main/java/io/nats/client/api/MessageInfo.java index 60374beed..39a4d633a 100644 --- a/src/main/java/io/nats/client/api/MessageInfo.java +++ b/src/main/java/io/nats/client/api/MessageInfo.java @@ -17,14 +17,14 @@ import io.nats.client.impl.Headers; import io.nats.client.support.DateTimeUtils; import io.nats.client.support.IncomingHeadersProcessor; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import java.time.ZonedDateTime; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.addRawJson; +import static io.nats.client.support.HeadersUtils.addHeadersAsField; import static io.nats.client.support.JsonValueUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.NatsJetStreamConstants.*; /** @@ -75,7 +75,7 @@ public MessageInfo(Message msg, String streamName, boolean direct) { lastSeq = -1; } else { - lastSeq = JsonUtils.safeParseLong(temp, -1); + lastSeq = safeParseLong(temp, -1); } // these are control headers, not real headers so don't give them to the user. headers = new Headers(msgHeaders, true, MESSAGE_INFO_HEADERS); @@ -160,22 +160,22 @@ public long getLastSeq() { @Override public String toString() { - StringBuilder sb = JsonUtils.beginJsonPrefixed("\"MessageInfo\":"); - JsonUtils.addField(sb, "direct", direct); - JsonUtils.addField(sb, "error", getError()); - JsonUtils.addField(sb, SUBJECT, subject); - JsonUtils.addField(sb, SEQ, seq); + StringBuilder sb = beginJsonPrefixed("\"MessageInfo\":"); + addField(sb, "direct", direct); + addField(sb, "error", getError()); + addField(sb, SUBJECT, subject); + addField(sb, SEQ, seq); if (data == null) { addRawJson(sb, DATA, "null"); } else { - JsonUtils.addField(sb, "data_length", data.length); + addField(sb, "data_length", data.length); } - JsonUtils.addField(sb, TIME, time); - JsonUtils.addField(sb, STREAM, stream); - JsonUtils.addField(sb, "last_seq", lastSeq); - JsonUtils.addField(sb, SUBJECT, subject); - JsonUtils.addField(sb, HDRS, headers); - return JsonUtils.endJson(sb).toString(); + addField(sb, TIME, time); + addField(sb, STREAM, stream); + addField(sb, "last_seq", lastSeq); + addField(sb, SUBJECT, subject); + addHeadersAsField(sb, HDRS, headers); + return endJson(sb).toString(); } } diff --git a/src/main/java/io/nats/client/api/ObjectInfo.java b/src/main/java/io/nats/client/api/ObjectInfo.java index 7f0123a35..dccb80f6f 100644 --- a/src/main/java/io/nats/client/api/ObjectInfo.java +++ b/src/main/java/io/nats/client/api/ObjectInfo.java @@ -19,8 +19,7 @@ import java.time.ZonedDateTime; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; +import static io.nats.client.support.JsonWriteUtils.*; /** * The ObjectInfo is Object Meta Information plus instance information @@ -72,12 +71,12 @@ public String toJson() { // never write MTIME (modified) StringBuilder sb = beginJson(); objectMeta.embedJson(sb); // the go code embeds the objectMeta's fields instead of as a child object. - JsonUtils.addField(sb, BUCKET, bucket); - JsonUtils.addField(sb, NUID, nuid); - JsonUtils.addField(sb, SIZE, size); - JsonUtils.addField(sb, CHUNKS, chunks); - JsonUtils.addField(sb, DIGEST, digest); - JsonUtils.addField(sb, DELETED, deleted); + addField(sb, BUCKET, bucket); + addField(sb, NUID, nuid); + addField(sb, SIZE, size); + addField(sb, CHUNKS, chunks); + addField(sb, DIGEST, digest); + addField(sb, DELETED, deleted); return endJson(sb).toString(); } diff --git a/src/main/java/io/nats/client/api/ObjectLink.java b/src/main/java/io/nats/client/api/ObjectLink.java index 346db7b7f..19b9c22fa 100644 --- a/src/main/java/io/nats/client/api/ObjectLink.java +++ b/src/main/java/io/nats/client/api/ObjectLink.java @@ -13,15 +13,13 @@ package io.nats.client.api; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import io.nats.client.support.Validator; import static io.nats.client.support.ApiConstants.BUCKET; import static io.nats.client.support.ApiConstants.NAME; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.readString; +import static io.nats.client.support.JsonWriteUtils.*; /** * The ObjectLink is used to embed links to other objects. @@ -48,8 +46,8 @@ private ObjectLink(String bucket, String objectName) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, BUCKET, bucket); - JsonUtils.addField(sb, NAME, objectName); + addField(sb, BUCKET, bucket); + addField(sb, NAME, objectName); return endJson(sb).toString(); } diff --git a/src/main/java/io/nats/client/api/ObjectMeta.java b/src/main/java/io/nats/client/api/ObjectMeta.java index aaf59d273..ed24bd925 100644 --- a/src/main/java/io/nats/client/api/ObjectMeta.java +++ b/src/main/java/io/nats/client/api/ObjectMeta.java @@ -14,14 +14,13 @@ import io.nats.client.impl.Headers; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import io.nats.client.support.Validator; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; +import static io.nats.client.support.HeadersUtils.addHeadersAsField; import static io.nats.client.support.JsonValueUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; /** * The ObjectMeta is Object Meta is high level information about an object @@ -60,14 +59,14 @@ public String toJson() { } void embedJson(StringBuilder sb) { - JsonUtils.addField(sb, NAME, objectName); - JsonUtils.addField(sb, DESCRIPTION, description); - JsonUtils.addField(sb, HEADERS, headers); + addField(sb, NAME, objectName); + addField(sb, DESCRIPTION, description); + addHeadersAsField(sb, HEADERS, headers); - // avoid adding an empty child to the json because JsonUtils.addField + // avoid adding an empty child to the json because addField // only checks versus the object being null, which it is never if (objectMetaOptions.hasData()) { - JsonUtils.addField(sb, OPTIONS, objectMetaOptions); + addField(sb, OPTIONS, objectMetaOptions); } } diff --git a/src/main/java/io/nats/client/api/ObjectMetaOptions.java b/src/main/java/io/nats/client/api/ObjectMetaOptions.java index f029a020c..20f621874 100644 --- a/src/main/java/io/nats/client/api/ObjectMetaOptions.java +++ b/src/main/java/io/nats/client/api/ObjectMetaOptions.java @@ -13,15 +13,13 @@ package io.nats.client.api; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import static io.nats.client.support.ApiConstants.LINK; import static io.nats.client.support.ApiConstants.MAX_CHUNK_SIZE; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.readInteger; import static io.nats.client.support.JsonValueUtils.readValue; +import static io.nats.client.support.JsonWriteUtils.*; /** * The ObjectMeta is Object Meta is high level information about an object. @@ -44,8 +42,8 @@ private ObjectMetaOptions(Builder b) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, LINK, link); - JsonUtils.addField(sb, MAX_CHUNK_SIZE, chunkSize); + addField(sb, LINK, link); + addField(sb, MAX_CHUNK_SIZE, chunkSize); return endJson(sb).toString(); } diff --git a/src/main/java/io/nats/client/api/Placement.java b/src/main/java/io/nats/client/api/Placement.java index e90bc6291..2473ec971 100644 --- a/src/main/java/io/nats/client/api/Placement.java +++ b/src/main/java/io/nats/client/api/Placement.java @@ -22,9 +22,9 @@ import static io.nats.client.support.ApiConstants.CLUSTER; import static io.nats.client.support.ApiConstants.TAGS; -import static io.nats.client.support.JsonUtils.*; import static io.nats.client.support.JsonValueUtils.readOptionalStringList; import static io.nats.client.support.JsonValueUtils.readString; +import static io.nats.client.support.JsonWriteUtils.*; /** * Placement directives to consider when placing replicas of a stream diff --git a/src/main/java/io/nats/client/api/Republish.java b/src/main/java/io/nats/client/api/Republish.java index dcf5832c4..7e9eb3503 100644 --- a/src/main/java/io/nats/client/api/Republish.java +++ b/src/main/java/io/nats/client/api/Republish.java @@ -18,9 +18,9 @@ import io.nats.client.support.Validator; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.*; import static io.nats.client.support.JsonValueUtils.readBoolean; import static io.nats.client.support.JsonValueUtils.readString; +import static io.nats.client.support.JsonWriteUtils.*; /** * Republish Configuration diff --git a/src/main/java/io/nats/client/api/SourceBase.java b/src/main/java/io/nats/client/api/SourceBase.java index 6102e5e01..c8e786847 100644 --- a/src/main/java/io/nats/client/api/SourceBase.java +++ b/src/main/java/io/nats/client/api/SourceBase.java @@ -14,7 +14,6 @@ package io.nats.client.api; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import io.nats.client.support.JsonValueUtils; @@ -26,9 +25,8 @@ import static io.nats.client.JetStreamOptions.convertDomainToPrefix; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.readValue; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.consumerFilterSubjectsAreEquivalent; public abstract class SourceBase implements JsonSerializable { @@ -64,12 +62,12 @@ public abstract class SourceBase implements JsonSerializable { */ public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, NAME, name); - JsonUtils.addFieldWhenGreaterThan(sb, OPT_START_SEQ, startSeq, 0); - JsonUtils.addField(sb, OPT_START_TIME, startTime); - JsonUtils.addField(sb, FILTER_SUBJECT, filterSubject); - JsonUtils.addField(sb, EXTERNAL, external); - JsonUtils.addJsons(sb, SUBJECT_TRANSFORMS, subjectTransforms); + 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(); } @@ -111,7 +109,7 @@ public List getSubjectTransforms() { @Override public String toString() { - return JsonUtils.toKey(getClass()) + toJson(); + return toKey(getClass()) + toJson(); } public abstract static class SourceBaseBuilder { diff --git a/src/main/java/io/nats/client/api/StreamConfiguration.java b/src/main/java/io/nats/client/api/StreamConfiguration.java index 20ee6b175..5a30f0a67 100644 --- a/src/main/java/io/nats/client/api/StreamConfiguration.java +++ b/src/main/java/io/nats/client/api/StreamConfiguration.java @@ -13,19 +13,17 @@ package io.nats.client.api; -import io.nats.client.support.*; +import io.nats.client.support.JsonParseException; +import io.nats.client.support.JsonParser; +import io.nats.client.support.JsonSerializable; +import io.nats.client.support.JsonValue; import java.time.Duration; import java.util.*; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.*; -import static io.nats.client.support.JsonValueUtils.readBoolean; -import static io.nats.client.support.JsonValueUtils.readInteger; -import static io.nats.client.support.JsonValueUtils.readLong; -import static io.nats.client.support.JsonValueUtils.readNanos; -import static io.nats.client.support.JsonValueUtils.readString; import static io.nats.client.support.JsonValueUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.*; /** @@ -163,7 +161,7 @@ public String toJson() { StringBuilder sb = beginJson(); addField(sb, NAME, name); - JsonUtils.addField(sb, DESCRIPTION, description); + addField(sb, DESCRIPTION, description); addStrings(sb, SUBJECTS, subjects); addField(sb, RETENTION, retentionPolicy.toString()); addEnumWhenNot(sb, COMPRESSION, compressionOption, CompressionOption.None); diff --git a/src/main/java/io/nats/client/api/StreamInfoOptions.java b/src/main/java/io/nats/client/api/StreamInfoOptions.java index 1df84ea88..fe383736e 100644 --- a/src/main/java/io/nats/client/api/StreamInfoOptions.java +++ b/src/main/java/io/nats/client/api/StreamInfoOptions.java @@ -17,7 +17,7 @@ import static io.nats.client.support.ApiConstants.DELETED_DETAILS; import static io.nats.client.support.ApiConstants.SUBJECTS_FILTER; -import static io.nats.client.support.JsonUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.emptyAsNull; /** diff --git a/src/main/java/io/nats/client/api/SubjectTransform.java b/src/main/java/io/nats/client/api/SubjectTransform.java index 4fb58c3a4..a08077dfc 100644 --- a/src/main/java/io/nats/client/api/SubjectTransform.java +++ b/src/main/java/io/nats/client/api/SubjectTransform.java @@ -22,8 +22,8 @@ import static io.nats.client.support.ApiConstants.DEST; import static io.nats.client.support.ApiConstants.SRC; -import static io.nats.client.support.JsonUtils.*; import static io.nats.client.support.JsonValueUtils.readString; +import static io.nats.client.support.JsonWriteUtils.*; /** * SubjectTransform diff --git a/src/main/java/io/nats/client/impl/StreamInfoReader.java b/src/main/java/io/nats/client/impl/StreamInfoReader.java index 4b5a3aa61..b2878247e 100644 --- a/src/main/java/io/nats/client/impl/StreamInfoReader.java +++ b/src/main/java/io/nats/client/impl/StreamInfoReader.java @@ -20,7 +20,7 @@ import static io.nats.client.support.ApiConstants.DELETED_DETAILS; import static io.nats.client.support.ApiConstants.SUBJECTS_FILTER; -import static io.nats.client.support.JsonUtils.*; +import static io.nats.client.support.JsonWriteUtils.*; class StreamInfoReader { diff --git a/src/main/java/io/nats/client/support/DateTimeUtils.java b/src/main/java/io/nats/client/support/DateTimeUtils.java deleted file mode 100644 index c84f53ac6..000000000 --- a/src/main/java/io/nats/client/support/DateTimeUtils.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2020 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -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'"); - - 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)); - } - - 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/main/java/io/nats/client/support/Encoding.java b/src/main/java/io/nats/client/support/Encoding.java deleted file mode 100644 index ab348bab5..000000000 --- a/src/main/java/io/nats/client/support/Encoding.java +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2020 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Base64; - -public abstract class Encoding { - private Encoding() {} /* ensures cannot be constructed */ - - /** - * base64 url encode a byte array to a byte array - * @param input the input byte array to encode - * @return the encoded byte array - * @deprecated prefer base64UrlEncode - */ - @Deprecated - public static byte[] base64Encode(byte[] input) { - return Base64.getUrlEncoder().withoutPadding().encode(input); - } - - /** - * base64 url encode a byte array to a byte array - * @param input the input byte array to encode - * @return the encoded byte array - */ - public static byte[] base64UrlEncode(byte[] input) { - return Base64.getUrlEncoder().withoutPadding().encode(input); - } - - /** - * base64 url encode a byte array to a string - * @param input the input byte array to encode - * @return the encoded string - */ - public static String toBase64Url(byte[] input) { - return new String(base64UrlEncode(input)); - } - - /** - * base64 url encode a string to a string - * @param input the input string to encode - * @return the encoded string - */ - public static String toBase64Url(String input) { - return new String(base64UrlEncode(input.getBytes(StandardCharsets.US_ASCII))); - } - - /** - * base64 url decode a byte array - * @param input the input byte array to decode - * @return the decoded byte array - */ - public static byte[] base64UrlDecode(byte[] input) { - return Base64.getUrlDecoder().decode(input); - } - - /** - * get a string from a base64 url encoded byte array - * @param input the input string to decode - * @return the decoded string - */ - public static String fromBase64Url(String input) { - return new String(base64UrlDecode(input.getBytes(StandardCharsets.US_ASCII))); - } - - // http://en.wikipedia.org/wiki/Base_32 - private static final String BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - private static final int[] BASE32_LOOKUP; - private static final int MASK = 31; - private static final int SHIFT = 5; - public static char[] base32Encode(final byte[] input) { - int last = input.length; - char[] charBuff = new char[(last + 7) * 8 / SHIFT]; - int offset = 0; - int buffer = input[offset++]; - int bitsLeft = 8; - int i = 0; - - while (bitsLeft > 0 || offset < last) { - if (bitsLeft < SHIFT) { - if (offset < last) { - buffer <<= 8; - buffer |= (input[offset++] & 0xff); - bitsLeft += 8; - } else { - int pad = SHIFT - bitsLeft; - buffer <<= pad; - bitsLeft += pad; - } - } - int index = MASK & (buffer >> (bitsLeft - SHIFT)); - bitsLeft -= SHIFT; - charBuff[i] = BASE32_CHARS.charAt(index); - i++; - } - - int nonBlank; - - for (nonBlank=charBuff.length-1;nonBlank>=0;nonBlank--) { - if (charBuff[nonBlank] != 0) { - break; - } - } - - char[] retVal = new char[nonBlank+1]; - - System.arraycopy(charBuff, 0, retVal, 0, retVal.length); - - Arrays.fill(charBuff, '\0'); - - return retVal; - } - static { - BASE32_LOOKUP = new int[256]; - - Arrays.fill(BASE32_LOOKUP, 0xFF); - - for (int i = 0; i < BASE32_CHARS.length(); i++) { - int index = BASE32_CHARS.charAt(i) - '0'; - BASE32_LOOKUP[index] = i; - } - } - - public static byte[] base32Decode(final char[] input) { - byte[] bytes = new byte[input.length * SHIFT / 8]; - int buffer = 0; - int next = 0; - int bitsLeft = 0; - - for (int i = 0; i < input.length; i++) { - int lookup = input[i] - '0'; - - if (lookup < 0 || lookup >= BASE32_LOOKUP.length) { - continue; - } - - int c = BASE32_LOOKUP[lookup]; - buffer <<= SHIFT; - buffer |= c & MASK; - bitsLeft += SHIFT; - if (bitsLeft >= 8) { - bytes[next++] = (byte) (buffer >> (bitsLeft - 8)); - bitsLeft -= 8; - } - } - return bytes; - } - - public static String jsonDecode(String s) { - int len = s.length(); - StringBuilder sb = new StringBuilder(len); - for (int x = 0; x < len; x++) { - char ch = s.charAt(x); - if (ch == '\\') { - char nextChar = (x == len - 1) ? '\\' : s.charAt(x + 1); - switch (nextChar) { - case '\\': - ch = '\\'; - break; - case 'b': - ch = '\b'; - break; - case 'f': - ch = '\f'; - break; - case 'n': - ch = '\n'; - break; - case 'r': - ch = '\r'; - break; - case 't': - ch = '\t'; - break; - // Hex Unicode: u???? - case 'u': - if (x >= len - 5) { - ch = 'u'; - break; - } - int code = Integer.parseInt( - "" + s.charAt(x + 2) + s.charAt(x + 3) + s.charAt(x + 4) + s.charAt(x + 5), 16); - sb.append(Character.toChars(code)); - x += 5; - continue; - default: - ch = nextChar; - break; - } - x++; - } - sb.append(ch); - } - return sb.toString(); - } - - public static String jsonEncode(String s) { - return jsonEncode(new StringBuilder(), s).toString(); - } - - public static StringBuilder jsonEncode(StringBuilder sb, String s) { - int len = s.length(); - for (int x = 0; x < len; x++) { - char ch = s.charAt(x); - switch (ch) { - case '"': - sb.append("\\\""); - break; - case '\\': - sb.append("\\\\"); - break; - case '\b': - sb.append("\\b"); - break; - case '\f': - sb.append("\\f"); - break; - case '\n': - sb.append("\\n"); - break; - case '\r': - sb.append("\\r"); - break; - case '\t': - sb.append("\\t"); - break; - case '/': - sb.append("\\/"); - break; - default: - if (ch < ' ') { - sb.append(String.format("\\u%04x", (int) ch)); - } - else { - sb.append(ch); - } - break; - } - } - return sb; - } - - public static String uriDecode(String source) { - try { - return URLDecoder.decode(source.replace("+", "%2B"), "UTF-8"); - } catch (UnsupportedEncodingException e) { - return source; - } - } -} diff --git a/src/main/java/io/nats/client/support/HeadersUtils.java b/src/main/java/io/nats/client/support/HeadersUtils.java new file mode 100644 index 000000000..e6f022980 --- /dev/null +++ b/src/main/java/io/nats/client/support/HeadersUtils.java @@ -0,0 +1,37 @@ +// Copyright 2024 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES 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 io.nats.client.impl.Headers; + +import java.util.List; +import java.util.Map; + +import static io.nats.client.support.JsonWriteUtils.*; + +public abstract class HeadersUtils { + + public static void addHeadersAsField(StringBuilder sb, String fname, Headers headers) { + if (headers != null && !headers.isEmpty()) { + sb.append(Q); + Encoding.jsonEncode(sb, fname); + sb.append("\":{"); + for (Map.Entry> entry : headers.entrySet()) { + addStrings(sb, entry.getKey(), entry.getValue()); + } + endJson(sb); + sb.append(","); + } + } +} diff --git a/src/main/java/io/nats/client/support/JsonParseException.java b/src/main/java/io/nats/client/support/JsonParseException.java deleted file mode 100644 index 2036d46cb..000000000 --- a/src/main/java/io/nats/client/support/JsonParseException.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.nats.client.support; - -import java.io.IOException; - -public class JsonParseException extends IOException { - public JsonParseException(String message) { - super(message); - } - - public JsonParseException(String message, Throwable cause) { - super(message, cause); - } - - public JsonParseException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/io/nats/client/support/JsonParser.java b/src/main/java/io/nats/client/support/JsonParser.java deleted file mode 100644 index 9d4538e61..000000000 --- a/src/main/java/io/nats/client/support/JsonParser.java +++ /dev/null @@ -1,439 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static io.nats.client.support.JsonValue.NULL; - -public class JsonParser { - - enum Option {KEEP_NULLS} - - public static JsonValue parse(char[] json) throws JsonParseException { - return new JsonParser(json, 0).parse(); - } - - public static JsonValue parse(char[] json, int startIndex) throws JsonParseException { - return new JsonParser(json, startIndex).parse(); - } - - public static JsonValue parse(char[] json, Option... options) throws JsonParseException { - return new JsonParser(json, 0, options).parse(); - } - - public static JsonValue parse(char[] json, int startIndex, Option... options) throws JsonParseException { - return new JsonParser(json, startIndex, options).parse(); - } - - public static JsonValue parse(String json) throws JsonParseException { - return new JsonParser(json.toCharArray(), 0).parse(); - } - - public static JsonValue parse(String json, int startIndex) throws JsonParseException { - return new JsonParser(json.toCharArray(), startIndex).parse(); - } - - public static JsonValue parse(String json, Option... options) throws JsonParseException { - return new JsonParser(json.toCharArray(), 0, options).parse(); - } - - public static JsonValue parse(String json, int startIndex, Option... options) throws JsonParseException { - return new JsonParser(json.toCharArray(), startIndex, options).parse(); - } - - public static JsonValue parse(byte[] json) throws JsonParseException { - return new JsonParser(new String(json, StandardCharsets.UTF_8).toCharArray(), 0).parse(); - } - - public static JsonValue parse(byte[] json, Option... options) throws JsonParseException { - return new JsonParser(new String(json, StandardCharsets.UTF_8).toCharArray(), 0, options).parse(); - } - - public static JsonValue parseUnchecked(char[] json) { - try { return parse(json); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(char[] json, int startIndex) { - try { return parse(json, startIndex); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(char[] json, Option... options) { - try { return parse(json, options); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(char[] json, int startIndex, Option... options) { - try { return parse(json, startIndex, options); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(String json) { - try { return parse(json); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(String json, int startIndex) { - try { return parse(json, startIndex); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(String json, Option... options) { - try { return parse(json, options); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(String json, int startIndex, Option... options) { - try { return parse(json, startIndex, options); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(byte[] json) { - try { return parse(json); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - public static JsonValue parseUnchecked(byte[] json, Option... options) { - try { return parse(json, options); } - catch (JsonParseException j) { throw new RuntimeException(j); } - } - - private final char[] json; - private final boolean keepNulls; - private final int len; - private int idx; - private int nextIdx; - private char previous; - private char current; - private char next; - - public JsonParser(char[] json) { - this(json, 0); - } - - public JsonParser(char[] json, Option... options) { - this(json, 0, options); - } - - public JsonParser(char[] json, int startIndex, Option... options) { - this.json = json; - - boolean kn = false; - for (Option o : options) { - if (o == Option.KEEP_NULLS) { - kn = true; - break; // b/c only option currently - } - } - keepNulls = kn; - - len = json == null ? 0 : json.length; - idx = startIndex; - if (startIndex < 0) { - throw new IllegalArgumentException("Invalid start index."); - } - nextIdx = -1; - previous = 0; - current = 0; - next = 0; - } - - public JsonValue parse() throws JsonParseException { - char c = peekToken(); - if (c == 0) { - return NULL; - } - return nextValue(); - } - - private JsonValue nextValue() throws JsonParseException { - char c = peekToken(); - if (c == 0) { - throw new JsonParseException("Unexpected end of data."); - } - if (c == '"') { - nextToken(); - return new JsonValue(nextString()); - } - if (c == '{') { - nextToken(); - return new JsonValue(nextObject()); - } - if (c == '[') { - nextToken(); - return new JsonValue(nextArray()); - } - return nextPrimitiveValue(); - } - - private List nextArray() throws JsonParseException { - List list = new ArrayList<>(); - char p = peekToken(); - while (p != ']') { - if (p == ',') { - nextToken(); // advance past the peek - } - else { - list.add(nextValue()); - } - p = peekToken(); - } - nextToken(); // advance past the peek - return list; - } - - private JsonValue nextPrimitiveValue() throws JsonParseException { - StringBuilder sb = new StringBuilder(); - char c = peekToken(); - while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { - sb.append(nextToken()); - c = peekToken(); - } - String string = sb.toString(); - if ("true".equalsIgnoreCase(string)) { - return new JsonValue(Boolean.TRUE); - } - if ("false".equalsIgnoreCase(string)) { - return new JsonValue(Boolean.FALSE); - } - if ("null".equalsIgnoreCase(string)) { - return JsonValue.NULL; - } - try { - return asNumber(string); - } - catch (Exception e) { - throw new JsonParseException("Invalid value."); - } - } - - // next object assumes you have already seen the starting { - private Map nextObject() throws JsonParseException { - Map map = new HashMap<>(); - String key; - while (true) { - char c = nextToken(); - switch (c) { - case 0: - throw new JsonParseException("Text must end with '}'"); - case '}': - return map; - case '{': - case '[': - if (previous == '{') { - throw new JsonParseException("Cannot directly nest another Object or Array."); - } - // fall through - default: - key = nextString(); - } - - c = nextToken(); - if (c != ':') { - throw new JsonParseException("Expected a ':' after a key."); - } - - JsonValue value = nextValue(); - if (value != NULL || keepNulls) { - map.put(key, value); - } - - switch (nextToken()) { - case ',': - if (peekToken() == '}') { - return map; // dangling comma - } - break; - case '}': - return map; - default: - throw new JsonParseException("Expected a ',' or '}'."); - } - } - } - - private char nextToken() { - peekToken(); - idx = nextIdx; - nextIdx = -1; - previous = current; - current = next; - next = 0; - return current; - } - - private char nextChar() { - previous = current; - if (idx == len) { - current = 0; - } - else { - current = json[idx++]; - } - next = 0; - nextIdx = -1; - return current; - } - - private char peekToken() { - if (nextIdx == -1) { - nextIdx = idx; - next = 0; - while (nextIdx < len) { - char c = json[nextIdx++]; - switch (c) { - case ' ': - case '\r': - case '\n': - case '\t': - continue; - } - return next = c; - } - } - return next; - } - - // next string assumes you have already seen the starting quote - private String nextString() throws JsonParseException { - StringBuilder sb = new StringBuilder(); - while (true) { - char c = nextChar(); - switch (c) { - case 0: - case '\n': - case '\r': - throw new JsonParseException("Unterminated string."); - case '\\': - c = nextChar(); - switch (c) { - case 'b': - sb.append('\b'); - break; - case 't': - sb.append('\t'); - break; - case 'n': - sb.append('\n'); - break; - case 'f': - sb.append('\f'); - break; - case 'r': - sb.append('\r'); - break; - case 'u': - sb.append(parseU()); - break; - case '"': - case '\'': - case '\\': - case '/': - sb.append(c); - break; - default: - throw new JsonParseException("Illegal escape."); - } - break; - default: - if (c == '"') { - return sb.toString(); - } - sb.append(c); - } - } - } - - private char[] parseU() throws JsonParseException { - char[] a = new char[4]; - for (int x = 0; x < 4; x++) { - a[x] = nextToken(); - if (a[x] == 0) { - throw new JsonParseException("Illegal escape."); - } - } - try { - int code = Integer.parseInt("" + a[0] + a[1] + a[2] + a[3], 16); - return Character.toChars(code); - } - catch (RuntimeException e) { - throw new JsonParseException("Illegal escape.", e); - } - } - - private JsonValue asNumber(String val) throws JsonParseException { - char initial = val.charAt(0); - if ((initial >= '0' && initial <= '9') || initial == '-') { - // decimal representation - if (isDecimalNotation(val)) { - // Use a BigDecimal all the time to keep the original - // representation. BigDecimal doesn't support -0.0, ensure we - // keep that by forcing a decimal. - try { - BigDecimal bd = new BigDecimal(val); - if(initial == '-' && BigDecimal.ZERO.compareTo(bd)==0) { - return new JsonValue(-0.0); - } - return new JsonValue(bd); - } catch (NumberFormatException retryAsDouble) { - // this is to support "Hex Floats" like this: 0x1.0P-1074 - try { - double d = Double.parseDouble(val); - if(Double.isNaN(d) || Double.isInfinite(d)) { - throw new JsonParseException("val ["+val+"] is not a valid number."); - } - return new JsonValue(d); - } catch (NumberFormatException ignore) { - throw new JsonParseException("val ["+val+"] is not a valid number."); - } - } - } - // block items like 00 01 etc. Java number parsers treat these as Octal. - if(initial == '0' && val.length() > 1) { - char at1 = val.charAt(1); - if(at1 >= '0' && at1 <= '9') { - throw new JsonParseException("val ["+val+"] is not a valid number."); - } - } else if (initial == '-' && val.length() > 2) { - char at1 = val.charAt(1); - char at2 = val.charAt(2); - if(at1 == '0' && at2 >= '0' && at2 <= '9') { - throw new JsonParseException("val ["+val+"] is not a valid number."); - } - } - BigInteger bi = new BigInteger(val); - if(bi.bitLength() <= 31){ - return new JsonValue(bi.intValue()); - } - if(bi.bitLength() <= 63){ - return new JsonValue(bi.longValue()); - } - return new JsonValue(bi); - } - throw new JsonParseException("val ["+val+"] is not a valid number."); - } - - private boolean isDecimalNotation(final String val) { - return val.indexOf('.') > -1 || val.indexOf('e') > -1 - || val.indexOf('E') > -1 || "-0".equals(val); - } -} diff --git a/src/main/java/io/nats/client/support/JsonSerializable.java b/src/main/java/io/nats/client/support/JsonSerializable.java deleted file mode 100644 index 05db533e7..000000000 --- a/src/main/java/io/nats/client/support/JsonSerializable.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2021-2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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.nio.charset.StandardCharsets; - -public interface JsonSerializable { - String toJson(); - - default byte[] serialize() { - return toJson().getBytes(StandardCharsets.UTF_8); - } - - default JsonValue toJsonValue() { - return JsonParser.parseUnchecked(toJson()); - } -} diff --git a/src/main/java/io/nats/client/support/JsonUtils.java b/src/main/java/io/nats/client/support/JsonUtils.java index d7612895e..82138392f 100644 --- a/src/main/java/io/nats/client/support/JsonUtils.java +++ b/src/main/java/io/nats/client/support/JsonUtils.java @@ -25,16 +25,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import static io.nats.client.support.DateTimeUtils.DEFAULT_TIME; -import static io.nats.client.support.Encoding.jsonDecode; -import static io.nats.client.support.Encoding.jsonEncode; -import static io.nats.client.support.JsonValueUtils.instance; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.NatsConstants.COLON; /** - * Internal json parsing helpers. - * Read helpers deprecated Prefer using the {@link JsonParser} + * Internal json reading and writing helpers. + * @deprecated This class has been extracted to the io.nats.client.support.JsonWriteUtils class in the json.java library. */ +@Deprecated public abstract class JsonUtils { public static final String EMPTY_JSON = "{}"; @@ -46,11 +44,6 @@ public abstract class JsonUtils { private static final String BEFORE_FIELD_RE = "\""; private static final String AFTER_FIELD_RE = "\"\\s*:\\s*"; - private static final String Q = "\""; - private static final String QCOLONQ = "\":\""; - private static final String QCOLON = "\":"; - private static final String QCOMMA = "\","; - private static final String COMMA = ","; public static final String OPENQ = "{\""; public static final String CLOSE = "}"; @@ -59,47 +52,39 @@ private JsonUtils() {} /* ensures cannot be constructed */ // ---------------------------------------------------------------------------------------------------- // BUILD A STRING OF JSON // ---------------------------------------------------------------------------------------------------- + @Deprecated public static StringBuilder beginJson() { - return new StringBuilder("{"); + return JsonWriteUtils.beginJson(); } + @Deprecated public static StringBuilder beginArray() { - return new StringBuilder("["); + return JsonWriteUtils.beginArray(); } + @Deprecated public static StringBuilder beginJsonPrefixed(String prefix) { - return prefix == null ? beginJson() - : new StringBuilder(prefix).append('{'); + return JsonWriteUtils.beginJsonPrefixed(prefix); } + @Deprecated public static StringBuilder endJson(StringBuilder sb) { - int lastIndex = sb.length() - 1; - if (sb.charAt(lastIndex) == ',') { - sb.setCharAt(lastIndex, '}'); - return sb; - } - sb.append("}"); - return sb; + return JsonWriteUtils.endJson(sb); } + @Deprecated public static StringBuilder endArray(StringBuilder sb) { - int lastIndex = sb.length() - 1; - if (sb.charAt(lastIndex) == ',') { - sb.setCharAt(lastIndex, ']'); - return sb; - } - sb.append("]"); - return sb; + return JsonWriteUtils.endArray(sb); } + @Deprecated public static StringBuilder beginFormattedJson() { - return new StringBuilder("{\n "); + return JsonWriteUtils.beginFormattedJson(); } + @Deprecated public static String endFormattedJson(StringBuilder sb) { - sb.setLength(sb.length()-1); - sb.append("\n}"); - return sb.toString().replaceAll(",", ",\n "); + return JsonWriteUtils.endFormattedJson(sb); } /** @@ -108,14 +93,9 @@ public static String endFormattedJson(StringBuilder sb) { * @param fname fieldname * @param json raw json */ + @Deprecated public static void addRawJson(StringBuilder sb, String fname, String json) { - if (json != null && json.length() > 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON); - sb.append(json); - sb.append(COMMA); - } + JsonWriteUtils.addRawJson(sb, fname, json); } /** @@ -124,14 +104,9 @@ public static void addRawJson(StringBuilder sb, String fname, String json) { * @param fname fieldname * @param value field value */ + @Deprecated public static void addField(StringBuilder sb, String fname, String value) { - if (value != null && value.length() > 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLONQ); - jsonEncode(sb, value); - sb.append(QCOMMA); - } + JsonWriteUtils.addField(sb, fname, value); } /** @@ -140,15 +115,9 @@ public static void addField(StringBuilder sb, String fname, String value) { * @param fname fieldname * @param value field value */ + @Deprecated public static void addFieldEvenEmpty(StringBuilder sb, String fname, String value) { - if (value == null) { - value = ""; - } - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLONQ); - jsonEncode(sb, value); - sb.append(QCOMMA); + JsonWriteUtils.addFieldEvenEmpty(sb, fname, value); } /** @@ -157,12 +126,9 @@ public static void addFieldEvenEmpty(StringBuilder sb, String fname, String valu * @param fname fieldname * @param value field value */ + @Deprecated public static void addField(StringBuilder sb, String fname, Boolean value) { - if (value != null) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value ? "true" : "false").append(COMMA); - } + JsonWriteUtils.addField(sb, fname, value); } /** @@ -171,10 +137,9 @@ public static void addField(StringBuilder sb, String fname, Boolean value) { * @param fname fieldname * @param value field value */ + @Deprecated public static void addFldWhenTrue(StringBuilder sb, String fname, Boolean value) { - if (value != null && value) { - addField(sb, fname, true); - } + JsonWriteUtils.addFldWhenTrue(sb, fname, value); } /** @@ -183,12 +148,9 @@ public static void addFldWhenTrue(StringBuilder sb, String fname, Boolean value) * @param fname fieldname * @param value field value */ + @Deprecated public static void addField(StringBuilder sb, String fname, Integer value) { - if (value != null && value >= 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value).append(COMMA); - } + JsonWriteUtils.addField(sb, fname, value); } /** @@ -197,12 +159,9 @@ public static void addField(StringBuilder sb, String fname, Integer value) { * @param fname fieldname * @param value field value */ + @Deprecated public static void addFieldWhenGtZero(StringBuilder sb, String fname, Integer value) { - if (value != null && value > 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value).append(COMMA); - } + JsonWriteUtils.addFieldWhenGtZero(sb, fname, value); } /** @@ -211,12 +170,9 @@ public static void addFieldWhenGtZero(StringBuilder sb, String fname, Integer va * @param fname fieldname * @param value field value */ + @Deprecated public static void addField(StringBuilder sb, String fname, Long value) { - if (value != null && value >= 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value).append(COMMA); - } + JsonWriteUtils.addField(sb, fname, value); } /** @@ -225,12 +181,9 @@ public static void addField(StringBuilder sb, String fname, Long value) { * @param fname fieldname * @param value field value */ + @Deprecated public static void addFieldWhenGtZero(StringBuilder sb, String fname, Long value) { - if (value != null && value > 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value).append(COMMA); - } + JsonWriteUtils.addFieldWhenGtZero(sb, fname, value); } /** @@ -239,12 +192,9 @@ public static void addFieldWhenGtZero(StringBuilder sb, String fname, Long value * @param fname fieldname * @param value field value */ + @Deprecated public static void addFieldWhenGteMinusOne(StringBuilder sb, String fname, Long value) { - if (value != null && value >= -1) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value).append(COMMA); - } + JsonWriteUtils.addFieldWhenGteMinusOne(sb, fname, value); } /** @@ -254,12 +204,9 @@ public static void addFieldWhenGteMinusOne(StringBuilder sb, String fname, Long * @param value field value * @param gt the number the value must be greater than */ + @Deprecated public static void addFieldWhenGreaterThan(StringBuilder sb, String fname, Long value, long gt) { - if (value != null && value > gt) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value).append(COMMA); - } + JsonWriteUtils.addFieldWhenGreaterThan(sb, fname, value, gt); } /** @@ -268,12 +215,9 @@ public static void addFieldWhenGreaterThan(StringBuilder sb, String fname, Long * @param fname fieldname * @param value duration value */ + @Deprecated public static void addFieldAsNanos(StringBuilder sb, String fname, Duration value) { - if (value != null && !value.isZero() && !value.isNegative()) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value.toNanos()).append(COMMA); - } + JsonWriteUtils.addFieldAsNanos(sb, fname, value); } /** @@ -282,25 +226,20 @@ public static void addFieldAsNanos(StringBuilder sb, String fname, Duration valu * @param fname fieldname * @param value JsonSerializable value */ + @Deprecated public static void addField(StringBuilder sb, String fname, JsonSerializable value) { - if (value != null) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLON).append(value.toJson()).append(COMMA); - } + JsonWriteUtils.addField(sb, fname, value); } + @Deprecated public static void addField(StringBuilder sb, String fname, Map map) { - if (map != null && map.size() > 0) { - addField(sb, fname, instance(map)); - } + JsonWriteUtils.addField(sb, fname, map); } @SuppressWarnings("rawtypes") + @Deprecated public static void addEnumWhenNot(StringBuilder sb, String fname, Enum e, Enum dontAddIfThis) { - if (e != null && e != dontAddIfThis) { - addField(sb, fname, e.toString()); - } + JsonWriteUtils.addEnumWhenNot(sb, fname, e, dontAddIfThis); } public interface ListAdder { @@ -315,9 +254,10 @@ public interface ListAdder { * @param list value list * @param adder implementation to add value, including its quotes if required */ + @Deprecated public static void _addList(StringBuilder sb, String fname, List list, ListAdder adder) { sb.append(Q); - jsonEncode(sb, fname); + Encoding.jsonEncode(sb, fname); sb.append("\":["); for (int i = 0; i < list.size(); i++) { if (i > 0) { @@ -334,10 +274,9 @@ public static void _addList(StringBuilder sb, String fname, List list, Li * @param fname fieldname * @param strings field value */ + @Deprecated public static void addStrings(StringBuilder sb, String fname, String[] strings) { - if (strings != null && strings.length > 0) { - _addStrings(sb, fname, Arrays.asList(strings)); - } + JsonWriteUtils.addStrings(sb, fname, strings); } /** @@ -346,18 +285,9 @@ public static void addStrings(StringBuilder sb, String fname, String[] strings) * @param fname fieldname * @param strings field value */ + @Deprecated public static void addStrings(StringBuilder sb, String fname, List strings) { - if (strings != null && strings.size() > 0) { - _addStrings(sb, fname, strings); - } - } - - private static void _addStrings(StringBuilder sb, String fname, List strings) { - _addList(sb, fname, strings, (sbs, s) -> { - sb.append(Q); - jsonEncode(sb, s); - sb.append(Q); - }); + JsonWriteUtils.addStrings(sb, fname, strings); } /** @@ -366,10 +296,9 @@ private static void _addStrings(StringBuilder sb, String fname, List str * @param fname fieldname * @param jsons field value */ + @Deprecated public static void addJsons(StringBuilder sb, String fname, List jsons) { - if (jsons != null && !jsons.isEmpty()) { - _addList(sb, fname, jsons, (sbs, s) -> sbs.append(s.toJson())); - } + JsonWriteUtils.addJsons(sb, fname, jsons); } /** @@ -378,10 +307,9 @@ public static void addJsons(StringBuilder sb, String fname, List durations) { - if (durations != null && durations.size() > 0) { - _addList(sb, fname, durations, (sbs, dur) -> sbs.append(dur.toNanos())); - } + JsonWriteUtils.addDurations(sb, fname, durations); } /** @@ -390,27 +318,14 @@ public static void addDurations(StringBuilder sb, String fname, List d * @param fname fieldname * @param zonedDateTime field value */ + @Deprecated public static void addField(StringBuilder sb, String fname, ZonedDateTime zonedDateTime) { - if (zonedDateTime != null && !DEFAULT_TIME.equals(zonedDateTime)) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append(QCOLONQ) - .append(DateTimeUtils.toRfc3339(zonedDateTime)) - .append(QCOMMA); - } + JsonWriteUtils.addField(sb, fname, zonedDateTime); } + @Deprecated public static void addField(StringBuilder sb, String fname, Headers headers) { - if (headers != null && headers.size() > 0) { - sb.append(Q); - jsonEncode(sb, fname); - sb.append("\":{"); - for (Map.Entry> entry : headers.entrySet()) { - addStrings(sb, entry.getKey(), entry.getValue()); - } - endJson(sb); - sb.append(","); - } + HeadersUtils.addHeadersAsField(sb, fname, headers); } // ---------------------------------------------------------------------------------------------------- @@ -421,8 +336,9 @@ public static String normalize(String s) { return Character.toString(s.charAt(0)).toUpperCase() + s.substring(1).toLowerCase(); } + @Deprecated public static String toKey(Class c) { - return "\"" + c.getSimpleName() + "\":"; + return JsonWriteUtils.toKey(c); } @Deprecated @@ -443,80 +359,26 @@ private static String indent(int level) { * @param o the object * @return the formatted string */ + @Deprecated public static String getFormatted(Object o) { - StringBuilder sb = new StringBuilder(); - int level = 0; - int arrayLevel = 0; - boolean lastWasClose = false; - boolean indentNext = true; - String indent = ""; - String s = o.toString(); - for (int x = 0; x < s.length(); x++) { - char c = s.charAt(x); - if (c == '{') { - if (arrayLevel > 0 && lastWasClose) { - sb.append(indent); - } - sb.append(c).append('\n'); - indent = indent(++level); - indentNext = true; - lastWasClose = false; - } - else if (c == '}') { - indent = indent(--level); - sb.append('\n').append(indent).append(c); - lastWasClose = true; - } - else if (c == ',') { - sb.append(",\n"); - indentNext = true; - } - else { - if (c == '[') { - arrayLevel++; - } - else if (c == ']') { - arrayLevel--; - } - if (indentNext) { - if (c != ' ') { - sb.append(indent).append(c); - indentNext = false; - } - } - else { - sb.append(c); - } - lastWasClose = lastWasClose && Character.isWhitespace(c); - } - } - return sb.toString(); + return JsonWriteUtils.getFormatted(o); } public static void printFormatted(Object o) { - System.out.println(getFormatted(o)); + System.out.println(JsonWriteUtils.getFormatted(o)); } // ---------------------------------------------------------------------------------------------------- // SAFE NUMBER PARSING HELPERS // ---------------------------------------------------------------------------------------------------- + @Deprecated public static Long safeParseLong(String s) { - try { - return Long.parseLong(s); - } - catch (Exception e1) { - try { - return Long.parseUnsignedLong(s); - } - catch (Exception e2) { - return null; - } - } + return JsonWriteUtils.safeParseLong(s); } + @Deprecated public static long safeParseLong(String s, long dflt) { - Long l = safeParseLong(s); - return l == null ? dflt : l; + return JsonWriteUtils.safeParseLong(s, dflt); } // ---------------------------------------------------------------------------------------------------- @@ -777,8 +639,8 @@ private static List toList(String arrayString) { String[] raw = arrayString.split(","); for (String s : raw) { String cleaned = s.trim().replace("\"", ""); - if (cleaned.length() > 0) { - list.add(jsonDecode(cleaned)); + if (!cleaned.isEmpty()) { + list.add(Encoding.jsonDecode(cleaned)); } } return list; @@ -841,7 +703,7 @@ public static String readString(String json, Pattern pattern) { @Deprecated public static String readString(String json, Pattern pattern, String dflt) { Matcher m = pattern.matcher(json); - return m.find() ? jsonDecode(m.group(1)) : dflt; + return m.find() ? Encoding.jsonDecode(m.group(1)) : dflt; } @Deprecated @@ -870,7 +732,7 @@ else if (c == '"') { sb.append(c); } } - return jsonDecode(sb.toString()); + return Encoding.jsonDecode(sb.toString()); } return dflt; } @@ -972,33 +834,14 @@ public static void readNanos(String json, Pattern pattern, Consumer c) } } + @Deprecated public static boolean listEquals(List l1, List l2) { - if (l1 == null) - { - return l2 == null; - } - - if (l2 == null) - { - return false; - } - - return l1.equals(l2); + return Validator.listEquals(l1, l2); } + @Deprecated public static boolean mapEquals(Map map1, Map map2) { - if (map1 == null) { - return map2 == null; - } - if (map2 == null || map1.size() != map2.size()) { - return false; - } - for (String key : map1.keySet()) { - if (!Objects.equals(map1.get(key), map2.get(key))) { - return false; - } - } - return true; + return Validator.mapEquals(map1, map2); } } diff --git a/src/main/java/io/nats/client/support/JsonValue.java b/src/main/java/io/nats/client/support/JsonValue.java deleted file mode 100644 index ba3e699b4..000000000 --- a/src/main/java/io/nats/client/support/JsonValue.java +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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.math.BigDecimal; -import java.math.BigInteger; -import java.util.*; - -import static io.nats.client.support.JsonUtils.*; - -public class JsonValue implements JsonSerializable { - - public enum Type { - STRING, BOOL, INTEGER, LONG, DOUBLE, FLOAT, BIG_DECIMAL, BIG_INTEGER, MAP, ARRAY, NULL; - } - - private static final char QUOTE = '"'; - private static final char COMMA = ','; - private static final String NULL_STR = "null"; - - public static final JsonValue NULL = new JsonValue(); - public static final JsonValue TRUE = new JsonValue(true); - public static final JsonValue FALSE = new JsonValue(false); - public static final JsonValue EMPTY_MAP = new JsonValue(Collections.unmodifiableMap(new HashMap<>())); - public static final JsonValue EMPTY_ARRAY = new JsonValue(Collections.unmodifiableList(new ArrayList<>())); - - public final String string; - public final Boolean bool; - public final Integer i; - public final Long l; - public final Double d; - public final Float f; - public final BigDecimal bd; - public final BigInteger bi; - public final Map map; - public final List array; - public final Type type; - public final Object object; - public final Number number; - - public final List mapOrder; - - public JsonValue() { - this(null, null, null, null, null, null, null, null, null, null); - } - - public JsonValue(String string) { - this(string, null, null, null, null, null, null, null, null, null); - } - - public JsonValue(char c) { - this("" + c, null, null, null, null, null, null, null, null, null); - } - - public JsonValue(Boolean bool) { - this(null, bool, null, null, null, null, null, null, null, null); - } - - public JsonValue(int i) { - this(null, null, i, null, null, null, null, null, null, null); - } - - public JsonValue(long l) { - this(null, null, null, l, null, null, null, null, null, null); - } - - public JsonValue(double d) { - this(null, null, null, null, d, null, null, null, null, null); - } - - public JsonValue(float f) { - this(null, null, null, null, null, f, null, null, null, null); - } - - public JsonValue(BigDecimal bd) { - this(null, null, null, null, null, null, bd, null, null, null); - } - - public JsonValue(BigInteger bi) { - this(null, null, null, null, null, null, null, bi, null, null); - } - - public JsonValue(Map map) { - this(null, null, null, null, null, null, null, null, map, null); - } - - public JsonValue(List list) { - this(null, null, null, null, null, null, null, null, null, list); - } - - public JsonValue(JsonValue[] values) { - this(null, null, null, null, null, null, null, null, null, values == null ? null : Arrays.asList(values)); - } - - private JsonValue(String string, Boolean bool, Integer i, Long l, Double d, Float f, BigDecimal bd, BigInteger bi, Map map, List array) { - this.map = map; - mapOrder = new ArrayList<>(); - this.array = array; - this.string = string; - this.bool = bool; - this.i = i; - this.l = l; - this.d = d; - this.f = f; - this.bd = bd; - this.bi = bi; - if (i != null) { - this.type = Type.INTEGER; - number = i; - object = number; - } - else if (l != null) { - this.type = Type.LONG; - number = l; - object = number; - } - else if (d != null) { - this.type = Type.DOUBLE; - number = this.d; - object = number; - } - else if (f != null) { - this.type = Type.FLOAT; - number = this.f; - object = number; - } - else if (bd != null) { - this.type = Type.BIG_DECIMAL; - number = this.bd; - object = number; - } - else if (bi != null) { - this.type = Type.BIG_INTEGER; - number = this.bi; - object = number; - } - else { - number = null; - if (map != null) { - this.type = Type.MAP; - object = map; - } - else if (string != null) { - this.type = Type.STRING; - object = string; - } - else if (bool != null) { - this.type = Type.BOOL; - object = bool; - } - else if (array != null) { - this.type = Type.ARRAY; - object = array; - } - else { - this.type = Type.NULL; - object = null; - } - } - } - - public String toString(Class c) { - return toString(c.getSimpleName()); - } - - public String toString(String key) { - return QUOTE + key + QUOTE + ":" + toJson(); - } - - @Override - public String toString() { - return toJson(); - } - - @Override - public JsonValue toJsonValue() { - return this; - } - - @Override - public String toJson() { - switch (type) { - case STRING: return valueString(string); - case BOOL: return valueString(bool); - case MAP: return valueString(map); - case ARRAY: return valueString(array); - case INTEGER: return i.toString(); - case LONG: return l.toString(); - case DOUBLE: return d.toString(); - case FLOAT: return f.toString(); - case BIG_DECIMAL: return bd.toString(); - case BIG_INTEGER: return bi.toString(); - default: return NULL_STR; - } - } - - private String valueString(String s) { - return QUOTE + Encoding.jsonEncode(s) + QUOTE; - } - - private String valueString(boolean b) { - return Boolean.toString(b).toLowerCase(); - } - - private String valueString(Map map) { - StringBuilder sbo = beginJson(); - if (!mapOrder.isEmpty()) { - for (String key : mapOrder) { - addField(sbo, key, map.get(key)); - } - } - else { - for (String key : map.keySet()) { - addField(sbo, key, map.get(key)); - } - } - return endJson(sbo).toString(); - } - - private String valueString(List list) { - StringBuilder sba = beginArray(); - for (JsonValue v : list) { - sba.append(v.toJson()); - sba.append(COMMA); - } - return endArray(sba).toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - JsonValue jsonValue = (JsonValue) o; - - if (type != jsonValue.type) return false; - if (!Objects.equals(map, jsonValue.map)) return false; - if (!Objects.equals(array, jsonValue.array)) return false; - if (!Objects.equals(string, jsonValue.string)) return false; - if (!Objects.equals(bool, jsonValue.bool)) return false; - if (!Objects.equals(i, jsonValue.i)) return false; - if (!Objects.equals(l, jsonValue.l)) return false; - if (!Objects.equals(d, jsonValue.d)) return false; - if (!Objects.equals(f, jsonValue.f)) return false; - if (!Objects.equals(bd, jsonValue.bd)) return false; - return Objects.equals(bi, jsonValue.bi); - } - - @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 != null ? type.hashCode() : 0); - return result; - } -} diff --git a/src/main/java/io/nats/client/support/JsonValueUtils.java b/src/main/java/io/nats/client/support/JsonValueUtils.java deleted file mode 100644 index b65cbde39..000000000 --- a/src/main/java/io/nats/client/support/JsonValueUtils.java +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.*; -import java.util.function.Function; - -import static io.nats.client.support.JsonValue.*; - -/** - * Internal json value helpers. - */ -public abstract class JsonValueUtils { - - private JsonValueUtils() {} /* ensures cannot be constructed */ - - public interface JsonValueSupplier { - T get(JsonValue v); - } - - public static T read(JsonValue jsonValue, String key, JsonValueSupplier valueSupplier) { - JsonValue v = jsonValue == null || jsonValue.map == null ? null : jsonValue.map.get(key); - return valueSupplier.get(v); - } - - public static JsonValue readValue(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> v); - } - - public static JsonValue readObject(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> v == null ? EMPTY_MAP : v); - } - - public static List readArray(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> v == null ? EMPTY_ARRAY.array : v.array); - } - - public static Map readStringStringMap(JsonValue jv, String key) { - JsonValue o = readObject(jv, key); - if (o.type == Type.MAP && o.map.size() > 0) { - Map temp = new HashMap<>(); - for (String k : o.map.keySet()) { - String value = readString(o, k); - if (value != null) { - temp.put(k, value); - } - } - return temp.isEmpty() ? null : temp; - } - return null; - } - - public static String readString(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> v == null ? null : v.string); - } - - public static String readString(JsonValue jsonValue, String key, String dflt) { - return read(jsonValue, key, v -> v == null ? dflt : v.string); - } - - public static ZonedDateTime readDate(JsonValue jsonValue, String key) { - return read(jsonValue, key, - v -> v == null || v.string == null ? null : DateTimeUtils.parseDateTimeThrowParseError(v.string)); - } - - public static Integer readInteger(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> v == null ? null : getInteger(v)); - } - - public static int readInteger(JsonValue jsonValue, String key, int dflt) { - return read(jsonValue, key, v -> { - if (v != null) { - Integer i = getInteger(v); - if (i != null) { - return i; - } - } - return dflt; - }); - } - - public static Long readLong(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> v == null ? null : getLong(v)); - } - - public static long readLong(JsonValue jsonValue, String key, long dflt) { - return read(jsonValue, key, v -> { - if (v != null) { - Long l = getLong(v); - if (l != null) { - return l; - } - } - return dflt; - }); - } - - public static boolean readBoolean(JsonValue jsonValue, String key) { - return readBoolean(jsonValue, key, false); - } - - public static Boolean readBoolean(JsonValue jsonValue, String key, Boolean dflt) { - return read(jsonValue, key, - v -> v == null || v.bool == null ? dflt : v.bool); - } - - public static Duration readNanos(JsonValue jsonValue, String key) { - Long l = readLong(jsonValue, key); - return l == null ? null : Duration.ofNanos(l); - } - - public static Duration readNanos(JsonValue jsonValue, String key, Duration dflt) { - Long l = readLong(jsonValue, key); - return l == null ? dflt : Duration.ofNanos(l); - } - - public static List listOf(JsonValue v, Function provider) { - List list = new ArrayList<>(); - if (v != null && v.array != null) { - for (JsonValue jv : v.array) { - T t = provider.apply(jv); - if (t != null) { - list.add(t); - } - } - } - return list; - } - - public static List optionalListOf(JsonValue v, Function provider) { - List list = listOf(v, provider); - return list.isEmpty() ? null : list; - } - - public static List readStringList(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> listOf(v, jv -> jv.string)); - } - - public static List readStringListIgnoreEmpty(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> listOf(v, jv -> { - if (jv.string != null) { - String s = jv.string.trim(); - if (!s.isEmpty()) { - return s; - } - } - return null; - })); - } - - public static List readOptionalStringList(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> optionalListOf(v, jv -> jv.string)); - } - - public static List readLongList(JsonValue jsonValue, String key) { - return read(jsonValue, key, v -> listOf(v, JsonValueUtils::getLong)); - } - public static List readNanosList(JsonValue jsonValue, String key) { - return readNanosList(jsonValue, key, false); - } - - public static List readNanosList(JsonValue jsonValue, String key, boolean nullIfEmpty) { - List list = read(jsonValue, key, - v -> listOf(v, vv -> { - Long l = getLong(vv); - return l == null ? null : Duration.ofNanos(l); - }) - ); - return list.isEmpty() && nullIfEmpty ? null : list; - } - - public static byte[] readBytes(JsonValue jsonValue, String key) { - String s = readString(jsonValue, key); - return s == null ? null : s.getBytes(StandardCharsets.US_ASCII); - } - - public static byte[] readBase64(JsonValue jsonValue, String key) { - String b64 = readString(jsonValue, key); - return b64 == null ? null : Base64.getDecoder().decode(b64); - } - - public static Integer getInteger(JsonValue v) { - if (v.i != null) { - return v.i; - } - // just in case the number was stored as a long, which is unlikely, but I want to handle it - if (v.l != null && v.l <= Integer.MAX_VALUE && v.l >= Integer.MIN_VALUE) { - return v.l.intValue(); - } - return null; - } - - public static Long getLong(JsonValue v) { - return v.l != null ? v.l : (v.i != null ? (long)v.i : null); - } - - public static long getLong(JsonValue v, long dflt) { - return v.l != null ? v.l : (v.i != null ? (long)v.i : dflt); - } - - public static JsonValue instance(Duration d) { - return new JsonValue(d.toNanos()); - } - - @SuppressWarnings("rawtypes") - public static JsonValue instance(Collection list) { - JsonValue v = new JsonValue(new ArrayList<>()); - for (Object o : list) { - v.array.add(toJsonValue(o)); - } - return v; - } - - @SuppressWarnings("rawtypes") - public static JsonValue instance(Map map) { - JsonValue v = new JsonValue(new HashMap<>()); - for (Object key : map.keySet()) { - v.map.put(key.toString(), toJsonValue(map.get(key))); - } - return v; - } - - public static JsonValue toJsonValue(Object o) { - if (o == null) { - return JsonValue.NULL; - } - if (o instanceof JsonValue) { - return (JsonValue)o; - } - if (o instanceof JsonSerializable) { - return ((JsonSerializable)o).toJsonValue(); - } - if (o instanceof Map) { - //noinspection unchecked,rawtypes - return new JsonValue((Map)o); - } - if (o instanceof List) { - //noinspection unchecked,rawtypes - return new JsonValue((List)o); - } - if (o instanceof Set) { - //noinspection unchecked,rawtypes - return new JsonValue(new ArrayList<>((Set)o)); - } - if (o instanceof String) { - String s = ((String)o).trim(); - return s.length() == 0 ? new JsonValue() : new JsonValue(s); - } - if (o instanceof Boolean) { - return new JsonValue((Boolean)o); - } - if (o instanceof Integer) { - return new JsonValue((Integer)o); - } - if (o instanceof Long) { - return new JsonValue((Long)o); - } - if (o instanceof Double) { - return new JsonValue((Double)o); - } - if (o instanceof Float) { - return new JsonValue((Float)o); - } - if (o instanceof BigDecimal) { - return new JsonValue((BigDecimal)o); - } - if (o instanceof BigInteger) { - return new JsonValue((BigInteger)o); - } - return new JsonValue(o.toString()); - } - - public static MapBuilder mapBuilder() { - return new MapBuilder(); - } - - public static class MapBuilder implements JsonSerializable { - public JsonValue jv; - - public MapBuilder() { - jv = new JsonValue(new HashMap<>()); - } - - public MapBuilder(JsonValue jv) { - this.jv = jv; - } - - public MapBuilder put(String s, Object o) { - if (o != null) { - JsonValue vv = JsonValueUtils.toJsonValue(o); - if (vv.type != JsonValue.Type.NULL) { - jv.map.put(s, vv); - jv.mapOrder.add(s); - } - } - return this; - } - - public MapBuilder put(String s, Map stringMap) { - if (stringMap != null) { - MapBuilder mb = new MapBuilder(); - for (String key : stringMap.keySet()) { - mb.put(key, stringMap.get(key)); - } - jv.map.put(s, mb.jv); - jv.mapOrder.add(s); - } - return this; - } - - @Override - public String toJson() { - return jv.toJson(); - } - - @Override - public JsonValue toJsonValue() { - return jv; - } - - @Deprecated - public JsonValue getJsonValue() { - return jv; - } - } - - public static ArrayBuilder arrayBuilder() { - return new ArrayBuilder(); - } - - public static class ArrayBuilder implements JsonSerializable { - public JsonValue jv = new JsonValue(new ArrayList<>()); - public ArrayBuilder add(Object o) { - if (o != null) { - JsonValue vv = JsonValueUtils.toJsonValue(o); - if (vv.type != JsonValue.Type.NULL) { - jv.array.add(JsonValueUtils.toJsonValue(o)); - } - } - return this; - } - - @Override - public String toJson() { - return jv.toJson(); - } - - @Override - public JsonValue toJsonValue() { - return jv; - } - - @Deprecated - public JsonValue getJsonValue() { - return jv; - } - } -} - diff --git a/src/main/java/io/nats/client/support/JwtUtils.java b/src/main/java/io/nats/client/support/JwtUtils.java index aa324b1e3..8e318f278 100644 --- a/src/main/java/io/nats/client/support/JwtUtils.java +++ b/src/main/java/io/nats/client/support/JwtUtils.java @@ -16,28 +16,22 @@ import io.nats.client.NKey; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; -import java.security.MessageDigest; import java.time.Duration; import java.util.List; -import static io.nats.client.support.Encoding.*; -import static io.nats.client.support.JsonUtils.beginJson; -import static io.nats.client.support.JsonUtils.endJson; +import static io.nats.client.support.JsonWriteUtils.beginJson; +import static io.nats.client.support.JsonWriteUtils.endJson; /** * Implements ADR-14 + * @deprecated This class has been extracted to the io.nats.jwt.JwtUtils class in the jwt.java library. */ +@Deprecated public abstract class JwtUtils { private JwtUtils() {} /* ensures cannot be constructed */ - private static final String ENCODED_CLAIM_HEADER = - toBase64Url("{\"typ\":\"JWT\", \"alg\":\"ed25519-nkey\"}"); - - private static final long NO_LIMIT = -1; - /** * Format string with `%s` placeholder for the JWT token followed * by the user NKey seed. This can be directly used as such: @@ -46,27 +40,17 @@ private JwtUtils() {} /* ensures cannot be constructed */ * NKey userKey = NKey.createUser(new SecureRandom()); * NKey signingKey = loadFromSecretStore(); * String jwt = issueUserJWT(signingKey, accountId, new String(userKey.getPublicKey())); - * String.format(JwtUtils.NATS_USER_JWT_FORMAT, jwt, new String(userKey.getSeed())); + * String.format(NATS_USER_JWT_FORMAT, jwt, new String(userKey.getSeed())); * */ - public static final String NATS_USER_JWT_FORMAT = "-----BEGIN NATS USER JWT-----\n" + - "%s\n" + - "------END NATS USER JWT------\n" + - "\n" + - "************************* IMPORTANT *************************\n" + - "NKEY Seed printed below can be used to sign and prove identity.\n" + - "NKEYs are sensitive and should be treated as secrets.\n" + - "\n" + - "-----BEGIN USER NKEY SEED-----\n" + - "%s\n" + - "------END USER NKEY SEED------\n" + - "\n" + - "*************************************************************\n"; + @Deprecated + public static final String NATS_USER_JWT_FORMAT = io.nats.jwt.JwtUtils.NATS_USER_JWT_FORMAT; /** * Get the current time in seconds since epoch. Used for issue time. * @return the time */ + @Deprecated public static long currentTimeSeconds() { return System.currentTimeMillis() / 1000; } @@ -82,8 +66,9 @@ public static long currentTimeSeconds() { * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueUserJWT(NKey signingKey, String accountId, String publicUserKey) throws GeneralSecurityException, IOException { - return issueUserJWT(signingKey, publicUserKey, null, null, currentTimeSeconds(), null, new UserClaim(accountId)); + return io.nats.jwt.JwtUtils.issueUserJWT(signingKey, publicUserKey, null, null, io.nats.jwt.JwtUtils.currentTimeSeconds(), null, new io.nats.jwt.UserClaim(accountId)); } /** @@ -98,8 +83,9 @@ public static String issueUserJWT(NKey signingKey, String accountId, String publ * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueUserJWT(NKey signingKey, String accountId, String publicUserKey, String name) throws GeneralSecurityException, IOException { - return issueUserJWT(signingKey, publicUserKey, name, null, currentTimeSeconds(), null, new UserClaim(accountId)); + return io.nats.jwt.JwtUtils.issueUserJWT(signingKey, publicUserKey, name, null, io.nats.jwt.JwtUtils.currentTimeSeconds(), null, new io.nats.jwt.UserClaim(accountId)); } /** @@ -116,8 +102,9 @@ public static String issueUserJWT(NKey signingKey, String accountId, String publ * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueUserJWT(NKey signingKey, String accountId, String publicUserKey, String name, Duration expiration, String... tags) throws GeneralSecurityException, IOException { - return issueUserJWT(signingKey, publicUserKey, name, expiration, currentTimeSeconds(), null, new UserClaim(accountId).tags(tags)); + return io.nats.jwt.JwtUtils.issueUserJWT(signingKey, accountId, publicUserKey, name, expiration, tags, null, null); } /** @@ -135,12 +122,30 @@ public static String issueUserJWT(NKey signingKey, String accountId, String publ * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueUserJWT(NKey signingKey, String accountId, String publicUserKey, String name, Duration expiration, String[] tags, long issuedAt) throws GeneralSecurityException, IOException { - return issueUserJWT(signingKey, publicUserKey, name, expiration, issuedAt, null, new UserClaim(accountId).tags(tags)); + return io.nats.jwt.JwtUtils.issueUserJWT(signingKey, accountId, publicUserKey, name, expiration, tags, issuedAt, null); } + /** + * Issue a user JWT from a scoped signing key. See Signing Keys + * @param signingKey a mandatory account nkey pair to sign the generated jwt. + * @param accountId a mandatory public account nkey. Will throw error when not set or not account nkey. + * @param publicUserKey a mandatory public user nkey. Will throw error when not set or not user nkey. + * @param name optional human-readable name. When absent, default to publicUserKey. + * @param expiration optional but recommended duration, when the generated jwt needs to expire. If not set, JWT will not expire. + * @param tags optional list of tags to be included in the JWT. + * @param issuedAt the current epoch seconds. + * @param audience the optional audience + * @throws IllegalArgumentException if the accountId or publicUserKey is not a valid public key of the proper type + * @throws NullPointerException if signingKey, accountId, or publicUserKey are null. + * @throws GeneralSecurityException if SHA-256 MessageDigest is missing, or if the signingKey can not be used for signing. + * @throws IOException if signingKey sign method throws this exception. + * @return a JWT + */ + @Deprecated public static String issueUserJWT(NKey signingKey, String accountId, String publicUserKey, String name, Duration expiration, String[] tags, long issuedAt, String audience) throws GeneralSecurityException, IOException { - return issueUserJWT(signingKey, publicUserKey, name, expiration, issuedAt, audience, new UserClaim(accountId).tags(tags)); + return io.nats.jwt.JwtUtils.issueUserJWT(signingKey, accountId, publicUserKey, name, expiration, tags, issuedAt, audience); } /** @@ -157,6 +162,7 @@ public static String issueUserJWT(NKey signingKey, String accountId, String publ * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueUserJWT(NKey signingKey, String publicUserKey, String name, Duration expiration, long issuedAt, UserClaim nats) throws GeneralSecurityException, IOException { return issueUserJWT(signingKey, publicUserKey, name, expiration, issuedAt, null, nats); } @@ -176,6 +182,7 @@ public static String issueUserJWT(NKey signingKey, String publicUserKey, String * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueUserJWT(NKey signingKey, String publicUserKey, String name, Duration expiration, long issuedAt, String audience, UserClaim nats) throws GeneralSecurityException, IOException { // Validate the signingKey: if (signingKey.getType() != NKey.Type.ACCOUNT) { @@ -195,7 +202,7 @@ public static String issueUserJWT(NKey signingKey, String publicUserKey, String String claimName = Validator.nullOrEmpty(name) ? publicUserKey : name; - return issueJWT(signingKey, publicUserKey, claimName, expiration, issuedAt, accSigningKeyPub, audience, nats); + return io.nats.jwt.JwtUtils.issueJWT(signingKey, publicUserKey, claimName, expiration, issuedAt, accSigningKeyPub, audience, nats); } /** @@ -211,8 +218,9 @@ public static String issueUserJWT(NKey signingKey, String publicUserKey, String * @throws IOException if signingKey sign method throws this exception. * @return a JWT */ + @Deprecated public static String issueJWT(NKey signingKey, String publicUserKey, String name, Duration expiration, long issuedAt, String accSigningKeyPub, JsonSerializable nats) throws GeneralSecurityException, IOException { - return issueJWT(signingKey, publicUserKey, name, expiration, issuedAt, accSigningKeyPub, null, nats); + return io.nats.jwt.JwtUtils.issueJWT(signingKey, publicUserKey, name, expiration, issuedAt, accSigningKeyPub, null, nats); } /** @@ -230,35 +238,9 @@ public static String issueJWT(NKey signingKey, String publicUserKey, String name * @throws GeneralSecurityException if SHA-256 MessageDigest is missing, or if the signingKey can not be used for signing. * @throws IOException if signingKey sign method throws this exception. */ + @Deprecated public static String issueJWT(NKey signingKey, String publicUserKey, String name, Duration expiration, long issuedAt, String accSigningKeyPub, String audience, JsonSerializable nats) throws GeneralSecurityException, IOException { - Claim claim = new Claim(); - claim.aud = audience; - claim.iat = issuedAt; - claim.iss = accSigningKeyPub; - claim.name = name; - claim.sub = publicUserKey; - claim.exp = expiration; - claim.nats = nats; - - // Issue At time is stored in unix seconds - String claimJson = claim.toJson(); - - // Compute jti, a base32 encoded sha256 hash - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - byte[] encoded = sha256.digest(claimJson.getBytes(StandardCharsets.US_ASCII)); - - claim.jti = new String(base32Encode(encoded)); - claimJson = claim.toJson(); - - // all three components (header/body/signature) are base64url encoded - String encBody = toBase64Url(claimJson); - - // compute the signature off of header + body (. included on purpose) - byte[] sig = (ENCODED_CLAIM_HEADER + "." + encBody).getBytes(StandardCharsets.UTF_8); - String encSig = toBase64Url(signingKey.sign(sig)); - - // append signature to header and body and return it - return ENCODED_CLAIM_HEADER + "." + encBody + "." + encSig; + return io.nats.jwt.JwtUtils.issueJWT(signingKey, publicUserKey, name, expiration, issuedAt, accSigningKeyPub, audience, nats); } /** @@ -266,10 +248,12 @@ public static String issueJWT(NKey signingKey, String publicUserKey, String name * @param jwt the encoded jwt * @return the claim body json */ + @Deprecated public static String getClaimBody(String jwt) { - return fromBase64Url(jwt.split("\\.")[1]); + return io.nats.jwt.JwtUtils.getClaimBody(jwt); } + @Deprecated public static class UserClaim implements JsonSerializable { public String issuerAccount; // User public String[] tags; // User/GenericFields @@ -281,9 +265,9 @@ public static class UserClaim implements JsonSerializable { public String[] src; // User/UserPermissionLimits/Limits/UserLimits public List times; // User/UserPermissionLimits/Limits/UserLimits public String locale; // User/UserPermissionLimits/Limits/UserLimits - public long subs = NO_LIMIT; // User/UserPermissionLimits/Limits/NatsLimits - public long data = NO_LIMIT; // User/UserPermissionLimits/Limits/NatsLimits - public long payload = NO_LIMIT; // User/UserPermissionLimits/Limits/NatsLimits + public long subs = -1; // User/UserPermissionLimits/Limits/NatsLimits + public long data = -1; // User/UserPermissionLimits/Limits/NatsLimits + public long payload = -1; // User/UserPermissionLimits/Limits/NatsLimits public boolean bearerToken; // User/UserPermissionLimits public String[] allowedConnectionTypes; // User/UserPermissionLimits @@ -294,21 +278,21 @@ public UserClaim(String issuerAccount) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, "issuer_account", issuerAccount); - JsonUtils.addStrings(sb, "tags", tags); - JsonUtils.addField(sb, "type", type); - JsonUtils.addField(sb, "version", version); - JsonUtils.addField(sb, "pub", pub); - JsonUtils.addField(sb, "sub", sub); - JsonUtils.addField(sb, "resp", resp); - JsonUtils.addStrings(sb, "src", src); - JsonUtils.addJsons(sb, "times", times); - JsonUtils.addField(sb, "times_location", locale); - JsonUtils.addFieldWhenGteMinusOne(sb, "subs", subs); - JsonUtils.addFieldWhenGteMinusOne(sb, "data", data); - JsonUtils.addFieldWhenGteMinusOne(sb, "payload", payload); - JsonUtils.addFldWhenTrue(sb, "bearer_token", bearerToken); - JsonUtils.addStrings(sb, "allowed_connection_types", allowedConnectionTypes); + JsonWriteUtils.addField(sb, "issuer_account", issuerAccount); + JsonWriteUtils.addStrings(sb, "tags", tags); + JsonWriteUtils.addField(sb, "type", type); + JsonWriteUtils.addField(sb, "version", version); + JsonWriteUtils.addField(sb, "pub", pub); + JsonWriteUtils.addField(sb, "sub", sub); + JsonWriteUtils.addField(sb, "resp", resp); + JsonWriteUtils.addStrings(sb, "src", src); + JsonWriteUtils.addJsons(sb, "times", times); + JsonWriteUtils.addField(sb, "times_location", locale); + JsonWriteUtils.addFieldWhenGteMinusOne(sb, "subs", subs); + JsonWriteUtils.addFieldWhenGteMinusOne(sb, "data", data); + JsonWriteUtils.addFieldWhenGteMinusOne(sb, "payload", payload); + JsonWriteUtils.addFldWhenTrue(sb, "bearer_token", bearerToken); + JsonWriteUtils.addStrings(sb, "allowed_connection_types", allowedConnectionTypes); return endJson(sb).toString(); } @@ -373,6 +357,7 @@ public UserClaim allowedConnectionTypes(String... allowedConnectionTypes) { } } + @Deprecated public static class TimeRange implements JsonSerializable { public String start; public String end; @@ -385,12 +370,13 @@ public TimeRange(String start, String end) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, "start", start); - JsonUtils.addField(sb, "end", end); + JsonWriteUtils.addField(sb, "start", start); + JsonWriteUtils.addField(sb, "end", end); return endJson(sb).toString(); } } + @Deprecated public static class ResponsePermission implements JsonSerializable { public int maxMsgs; public Duration expires; @@ -413,8 +399,8 @@ public ResponsePermission expires(long expiresMillis) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, "max", maxMsgs); - JsonUtils.addFieldAsNanos(sb, "ttl", expires); + JsonWriteUtils.addField(sb, "max", maxMsgs); + JsonWriteUtils.addFieldAsNanos(sb, "ttl", expires); return endJson(sb).toString(); } } @@ -436,12 +422,13 @@ public Permission deny(String... deny) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addStrings(sb, "allow", allow); - JsonUtils.addStrings(sb, "deny", deny); + JsonWriteUtils.addStrings(sb, "allow", allow); + JsonWriteUtils.addStrings(sb, "deny", deny); return endJson(sb).toString(); } } + @Deprecated static class Claim implements JsonSerializable { String aud; String jti; @@ -455,19 +442,19 @@ static class Claim implements JsonSerializable { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, "aud", aud); - JsonUtils.addFieldEvenEmpty(sb, "jti", jti); - JsonUtils.addField(sb, "iat", iat); - JsonUtils.addField(sb, "iss", iss); - JsonUtils.addField(sb, "name", name); - JsonUtils.addField(sb, "sub", sub); + JsonWriteUtils.addField(sb, "aud", aud); + JsonWriteUtils.addFieldEvenEmpty(sb, "jti", jti); + JsonWriteUtils.addField(sb, "iat", iat); + JsonWriteUtils.addField(sb, "iss", iss); + JsonWriteUtils.addField(sb, "name", name); + JsonWriteUtils.addField(sb, "sub", sub); if (exp != null && !exp.isZero() && !exp.isNegative()) { long seconds = exp.toMillis() / 1000; - JsonUtils.addField(sb, "exp", iat + seconds); // relative to the iat + JsonWriteUtils.addField(sb, "exp", iat + seconds); // relative to the iat } - JsonUtils.addField(sb, "nats", nats); + JsonWriteUtils.addField(sb, "nats", nats); return endJson(sb).toString(); } } diff --git a/src/main/java/io/nats/client/support/Validator.java b/src/main/java/io/nats/client/support/Validator.java index c36659fa7..c476abdf7 100644 --- a/src/main/java/io/nats/client/support/Validator.java +++ b/src/main/java/io/nats/client/support/Validator.java @@ -17,6 +17,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -639,4 +640,33 @@ public static boolean mapsAreEquivalent(Map m1, Map boolean listEquals(List l1, List l2) + { + if (l1 == null) + { + return l2 == null; + } + + if (l2 == null) + { + return false; + } + + return l1.equals(l2); + } + + public static boolean mapEquals(Map map1, Map map2) { + if (map1 == null) { + return map2 == null; + } + if (map2 == null || map1.size() != map2.size()) { + return false; + } + for (String key : map1.keySet()) { + if (!Objects.equals(map1.get(key), map2.get(key))) { + return false; + } + } + return true; + } } diff --git a/src/main/java/io/nats/service/Endpoint.java b/src/main/java/io/nats/service/Endpoint.java index ad566ee29..17ffd79ac 100644 --- a/src/main/java/io/nats/service/Endpoint.java +++ b/src/main/java/io/nats/service/Endpoint.java @@ -14,7 +14,6 @@ package io.nats.service; import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import io.nats.client.support.Validator; @@ -23,9 +22,9 @@ import java.util.Objects; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.readString; import static io.nats.client.support.JsonValueUtils.readStringStringMap; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.validateIsRestrictedTerm; /** @@ -129,17 +128,17 @@ public Endpoint(String name, String subject, String queueGroup, Map listOf(JsonValue vEndpointStats) { @Override public String toJson() { StringBuilder sb = beginJson(); - JsonUtils.addField(sb, NAME, name); - JsonUtils.addField(sb, SUBJECT, subject); - JsonUtils.addField(sb, QUEUE_GROUP, queueGroup); - JsonUtils.addFieldWhenGtZero(sb, NUM_REQUESTS, numRequests); - JsonUtils.addFieldWhenGtZero(sb, NUM_ERRORS, numErrors); - JsonUtils.addFieldWhenGtZero(sb, PROCESSING_TIME, processingTime); - JsonUtils.addFieldWhenGtZero(sb, AVERAGE_PROCESSING_TIME, averageProcessingTime); - JsonUtils.addField(sb, LAST_ERROR, lastError); - JsonUtils.addField(sb, DATA, data); - JsonUtils.addField(sb, STARTED, started); + addField(sb, NAME, name); + addField(sb, SUBJECT, subject); + addField(sb, QUEUE_GROUP, queueGroup); + addFieldWhenGtZero(sb, NUM_REQUESTS, numRequests); + addFieldWhenGtZero(sb, NUM_ERRORS, numErrors); + addFieldWhenGtZero(sb, PROCESSING_TIME, processingTime); + addFieldWhenGtZero(sb, AVERAGE_PROCESSING_TIME, averageProcessingTime); + addField(sb, LAST_ERROR, lastError); + addField(sb, DATA, data); + addField(sb, STARTED, started); return endJson(sb).toString(); } @@ -217,7 +215,7 @@ public ZonedDateTime getStarted() { @Override public String toString() { - return JsonUtils.toKey(getClass()) + toJson(); + return toKey(getClass()) + toJson(); } @Override diff --git a/src/main/java/io/nats/service/InfoResponse.java b/src/main/java/io/nats/service/InfoResponse.java index fbbbb4f80..838e8a022 100644 --- a/src/main/java/io/nats/service/InfoResponse.java +++ b/src/main/java/io/nats/service/InfoResponse.java @@ -13,15 +13,16 @@ package io.nats.service; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; +import io.nats.client.support.Validator; import java.util.*; import static io.nats.client.support.ApiConstants.DESCRIPTION; import static io.nats.client.support.ApiConstants.ENDPOINTS; -import static io.nats.client.support.JsonUtils.listEquals; import static io.nats.client.support.JsonValueUtils.*; +import static io.nats.client.support.JsonWriteUtils.addField; +import static io.nats.client.support.JsonWriteUtils.addJsons; /** * Info response class forms the info json payload, for example: @@ -60,8 +61,8 @@ private InfoResponse(JsonValue jv) { @Override protected void subToJson(StringBuilder sb) { - JsonUtils.addField(sb, DESCRIPTION, description); - JsonUtils.addJsons(sb, ENDPOINTS, endpoints); + addField(sb, DESCRIPTION, description); + addJsons(sb, ENDPOINTS, endpoints); } /** @@ -89,7 +90,7 @@ public boolean equals(Object o) { InfoResponse that = (InfoResponse) o; if (!Objects.equals(description, that.description)) return false; - return listEquals(endpoints, that.endpoints); + return Validator.listEquals(endpoints, that.endpoints); } @Override diff --git a/src/main/java/io/nats/service/Service.java b/src/main/java/io/nats/service/Service.java index 2f3b34274..bf9dc537a 100644 --- a/src/main/java/io/nats/service/Service.java +++ b/src/main/java/io/nats/service/Service.java @@ -16,7 +16,6 @@ import io.nats.client.Connection; import io.nats.client.Dispatcher; import io.nats.client.support.DateTimeUtils; -import io.nats.client.support.JsonUtils; import java.time.Duration; import java.time.ZonedDateTime; @@ -28,7 +27,7 @@ import java.util.concurrent.TimeUnit; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.endJson; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Validator.nullOrEmpty; /** @@ -349,11 +348,11 @@ public EndpointStats getEndpointStats(String endpointName) { @Override public String toString() { - StringBuilder sb = JsonUtils.beginJsonPrefixed("\"Service\":"); - JsonUtils.addField(sb, ID, infoResponse.getId()); - JsonUtils.addField(sb, NAME, infoResponse.getName()); - JsonUtils.addField(sb, VERSION, infoResponse.getVersion()); - JsonUtils.addField(sb, DESCRIPTION, infoResponse.getDescription()); + StringBuilder sb = beginJsonPrefixed("\"Service\":"); + addField(sb, ID, infoResponse.getId()); + addField(sb, NAME, infoResponse.getName()); + addField(sb, VERSION, infoResponse.getVersion()); + addField(sb, DESCRIPTION, infoResponse.getDescription()); return endJson(sb).toString(); } } diff --git a/src/main/java/io/nats/service/ServiceResponse.java b/src/main/java/io/nats/service/ServiceResponse.java index 9e8bdba56..be45c9566 100644 --- a/src/main/java/io/nats/service/ServiceResponse.java +++ b/src/main/java/io/nats/service/ServiceResponse.java @@ -20,9 +20,9 @@ import java.util.Objects; import static io.nats.client.support.ApiConstants.*; -import static io.nats.client.support.JsonUtils.endJson; import static io.nats.client.support.JsonValueUtils.readString; import static io.nats.client.support.JsonValueUtils.readStringStringMap; +import static io.nats.client.support.JsonWriteUtils.*; /** * Base class for service responses Info, Ping and Stats @@ -114,20 +114,20 @@ protected void subToJson(StringBuilder sb) {} @Override public String toJson() { - StringBuilder sb = JsonUtils.beginJson(); - JsonUtils.addField(sb, ID, id); - JsonUtils.addField(sb, NAME, name); - JsonUtils.addField(sb, VERSION, version); + StringBuilder sb = beginJson(); + addField(sb, ID, id); + addField(sb, NAME, name); + addField(sb, VERSION, version); subToJson(sb); - JsonUtils.addField(sb, TYPE, type); - JsonUtils.addField(sb, METADATA, metadata); + addField(sb, TYPE, type); + addField(sb, METADATA, metadata); return endJson(sb).toString(); } @Override public String toString() { - return JsonUtils.toKey(getClass()) + toJson(); + return toKey(getClass()) + toJson(); } @Override @@ -141,7 +141,7 @@ public boolean equals(Object o) { if (!Objects.equals(name, that.name)) return false; if (!Objects.equals(id, that.id)) return false; if (!Objects.equals(version, that.version)) return false; - return JsonUtils.mapEquals(metadata, that.metadata); + return Validator.mapEquals(metadata, that.metadata); } @Override diff --git a/src/main/java/io/nats/service/StatsResponse.java b/src/main/java/io/nats/service/StatsResponse.java index 2723d3a4a..63855c706 100644 --- a/src/main/java/io/nats/service/StatsResponse.java +++ b/src/main/java/io/nats/service/StatsResponse.java @@ -13,7 +13,6 @@ package io.nats.service; -import io.nats.client.support.JsonUtils; import io.nats.client.support.JsonValue; import java.time.ZonedDateTime; @@ -24,6 +23,8 @@ import static io.nats.client.support.ApiConstants.STARTED; import static io.nats.client.support.JsonValueUtils.readDate; import static io.nats.client.support.JsonValueUtils.readValue; +import static io.nats.client.support.JsonWriteUtils.addField; +import static io.nats.client.support.JsonWriteUtils.addJsons; /** * Stats response class forms the stats json payload, for example: @@ -87,8 +88,8 @@ private StatsResponse(JsonValue jv) { @Override protected void subToJson(StringBuilder sb) { - JsonUtils.addJsons(sb, ENDPOINTS, endpointStatsList); - JsonUtils.addField(sb, STARTED, started); + addJsons(sb, ENDPOINTS, endpointStatsList); + addField(sb, STARTED, started); } /** diff --git a/src/test/java/io/nats/client/AuthTests.java b/src/test/java/io/nats/client/AuthTests.java index 6be7fb738..e6209636d 100644 --- a/src/test/java/io/nats/client/AuthTests.java +++ b/src/test/java/io/nats/client/AuthTests.java @@ -16,7 +16,6 @@ import io.nats.client.Connection.Status; import io.nats.client.ConnectionListener.Events; import io.nats.client.impl.TestHandler; -import io.nats.client.support.JwtUtils; import io.nats.client.utils.ResourceUtils; import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Test; @@ -32,6 +31,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static io.nats.jwt.JwtUtils.NATS_USER_JWT_FORMAT; +import static io.nats.jwt.JwtUtils.issueUserJWT; import static org.junit.jupiter.api.Assertions.*; public class AuthTests extends TestBase { @@ -713,9 +714,9 @@ else if (error.equalsIgnoreCase("authorization violation")) { long expires = 2500; long wait = 5000; Duration expiration = Duration.ofMillis(expires); - String jwt = JwtUtils.issueUserJWT(nKeyAccount, accountId, publicUserKey, "jnatsTestUser", expiration); + String jwt = issueUserJWT(nKeyAccount, accountId, publicUserKey, "jnatsTestUser", expiration); - String creds = String.format(JwtUtils.NATS_USER_JWT_FORMAT, jwt, new String(nKeyUser.getSeed())); + String creds = String.format(NATS_USER_JWT_FORMAT, jwt, new String(nKeyUser.getSeed())); String credsFile = ResourceUtils.createTempFile("nats_java_test", ".creds", creds.split("\\Q\\n\\E")); try (NatsTestServer ts = new NatsTestServer("src/test/resources/operatorJnatsTest.conf", false)) { diff --git a/src/test/java/io/nats/client/NKeyTests.java b/src/test/java/io/nats/client/NKeyTests.java deleted file mode 100644 index 0749b15e9..000000000 --- a/src/test/java/io/nats/client/NKeyTests.java +++ /dev/null @@ -1,600 +0,0 @@ -// Copyright 2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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; - -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; - -import static io.nats.client.NKey.removePaddingAndClear; -import static io.nats.client.support.Encoding.base32Decode; -import static io.nats.client.support.Encoding.base32Encode; -import static io.nats.client.utils.ResourceUtils.dataAsLines; -import static org.junit.jupiter.api.Assertions.*; - -public class NKeyTests { - private static final int ED25519_SIGNATURE_SIZE = 64; - - @Test - public void testCRC16() { - // Example inputs and outputs from around the web - byte[][] inputs = { - {}, - "abc".getBytes(StandardCharsets.US_ASCII), - "ABC".getBytes(StandardCharsets.US_ASCII), - "This is a string".getBytes(StandardCharsets.US_ASCII), - "123456789".getBytes(StandardCharsets.US_ASCII), - "abcdefghijklmnopqrstuvwxyz0123456789".getBytes(StandardCharsets.US_ASCII), - {(byte) 0x7F}, - {(byte) 0x80}, - {(byte) 0xFF}, - {0x0, 0x1, 0x7D, 0x7E, (byte) 0x7F, (byte) 0x80, (byte) 0xFE, (byte) 0xFF} - }; - - int[] expected = { - 0x0, // "" - 0x9DD6, // "abc" - 0x3994, // "ABC" - 0x21E3, // "This is a string" - 0x31C3, // "123456789" - 0xCBDE, // "abcdefghijklmnopqrstuvwxyz0123456789" - 0x8F78, // 0x7F - 0x9188, // 0x80 - 0x1EF0, // 0xFF - 0xE26F, // {0x0,0x1,0x7D,0x7E, 0x7F, 0x80, 0xFE, 0xFF} - }; - - for (int i = 0; i < inputs.length; i++) { - byte[] input = inputs[i]; - int crc = expected[i]; - int actual = NKey.crc16(input); - assertEquals(crc, actual, String.format("CRC for \"%s\", should be 0x%08X but was 0x%08X", Arrays.toString(input), crc, actual)); - } - } - - @Test - public void testBase32() { - List inputs = dataAsLines("utf8-test-strings.txt"); - - for (String expected : inputs) { - byte[] bytes = expected.getBytes(StandardCharsets.UTF_8); - char[] encoded = base32Encode(bytes); - byte[] decoded = base32Decode(encoded); - String test = new String(decoded, StandardCharsets.UTF_8); - assertEquals(test, expected); - } - - // bad input for coverage - byte[] decoded = base32Decode("/".toCharArray()); - assertEquals(0, decoded.length); - decoded = base32Decode(Character.toChars(512)); - assertEquals(0, decoded.length); - } - - @Test - public void testEncodeDecodeSeed() throws Exception { - byte[] bytes = new byte[64]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - char[] encoded = NKey.encodeSeed(NKey.Type.ACCOUNT, bytes); - DecodedSeed decoded = NKey.decodeSeed(encoded); - - assertEquals(NKey.Type.fromPrefix(decoded.prefix), NKey.Type.ACCOUNT); - assertTrue(Arrays.equals(bytes, decoded.bytes)); - } - - @Test - public void testEncodeDecode() throws Exception { - byte[] bytes = new byte[32]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - char[] encoded = NKey.encode(NKey.Type.ACCOUNT, bytes); - byte[] decoded = NKey.decode(NKey.Type.ACCOUNT, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); - - encoded = NKey.encode(NKey.Type.USER, bytes); - decoded = NKey.decode(NKey.Type.USER, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); - - encoded = NKey.encode(NKey.Type.SERVER, bytes); - decoded = NKey.decode(NKey.Type.SERVER, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); - - encoded = NKey.encode(NKey.Type.CLUSTER, bytes); - decoded = NKey.decode(NKey.Type.CLUSTER, encoded, false); - assertTrue(Arrays.equals(bytes, decoded)); - } - - @Test - public void testDecodeWrongType() { - assertThrows(IllegalArgumentException.class, () -> { - byte[] bytes = new byte[32]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - char[] encoded = NKey.encode(NKey.Type.ACCOUNT, bytes); - NKey.decode(NKey.Type.USER, encoded, false); - }); - } - - @Test - public void testEncodeSeedSize() { - assertThrows(IllegalArgumentException.class, () -> { - byte[] bytes = new byte[48]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - NKey.encodeSeed(NKey.Type.ACCOUNT, bytes); - }); - } - - @Test - public void testDecodeSize() { - assertThrows(IllegalArgumentException.class, () -> NKey.decode(NKey.Type.ACCOUNT, "".toCharArray(), false)); - } - - @Test - public void testBadCRC() throws Exception { - for (int i = 0; i < 10000; i++) { - try { - byte[] bytes = new byte[32]; - SecureRandom random = new SecureRandom(); - random.nextBytes(bytes); - - char[] encoded = NKey.encode(NKey.Type.ACCOUNT, bytes); - - StringBuilder builder = new StringBuilder(); - - for (int j = 0; j < encoded.length; j++) { - if (j == 6) { - char c = encoded[j]; - if (c == 'x' || c == 'X') { - builder.append('Z'); - } else { - builder.append('X'); - } - } else { - builder.append(encoded[j]); - } - } - - NKey.decode(NKey.Type.ACCOUNT, builder.toString().toCharArray(), false); - fail(); - } catch (IllegalArgumentException e) { - //expected - } - } - } - - @Test - public void testAccount() throws Exception { - NKey theKey = NKey.createAccount(null); - assertNotNull(theKey); - - char[] seed = theKey.getSeed(); - NKey.decodeSeed(seed); // throws if there is an issue - - assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); - - char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'A'); - - char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); - - byte[] data = "Synadia".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); - - assertTrue(theKey.verify(data, sig)); - - NKey otherKey = NKey.createAccount(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - - assertTrue(NKey.isValidPublicAccountKey(publicKey)); - assertFalse(NKey.isValidPublicClusterKey(publicKey)); - assertFalse(NKey.isValidPublicOperatorKey(publicKey)); - assertFalse(NKey.isValidPublicUserKey(publicKey)); - assertFalse(NKey.isValidPublicServerKey(publicKey)); - } - - @Test - public void testUser() throws Exception { - NKey theKey = NKey.createUser(null); - assertNotNull(theKey); - - char[] seed = theKey.getSeed(); - NKey.decodeSeed(seed); // throws if there is an issue - - assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); - - char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'U'); - - char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); - - byte[] data = "Mister Zero".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); - - assertTrue(theKey.verify(data, sig)); - - NKey otherKey = NKey.createUser(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - - assertTrue(NKey.isValidPublicUserKey(publicKey)); - assertFalse(NKey.isValidPublicAccountKey(publicKey)); - assertFalse(NKey.isValidPublicClusterKey(publicKey)); - assertFalse(NKey.isValidPublicOperatorKey(publicKey)); - assertFalse(NKey.isValidPublicServerKey(publicKey)); - } - - @Test - public void testCluster() throws Exception { - NKey theKey = NKey.createCluster(null); - assertNotNull(theKey); - - char[] seed = theKey.getSeed(); - NKey.decodeSeed(seed); // throws if there is an issue - - assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); - - char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'C'); - - char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); - - byte[] data = "Connect Everything".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); - - assertTrue(theKey.verify(data, sig)); - - NKey otherKey = NKey.createCluster(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - - assertTrue(NKey.isValidPublicClusterKey(publicKey)); - assertFalse(NKey.isValidPublicAccountKey(publicKey)); - assertFalse(NKey.isValidPublicOperatorKey(publicKey)); - assertFalse(NKey.isValidPublicUserKey(publicKey)); - assertFalse(NKey.isValidPublicServerKey(publicKey)); - } - - @Test - public void testOperator() throws Exception { - NKey theKey = NKey.createOperator(null); - assertNotNull(theKey); - - char[] seed = theKey.getSeed(); - NKey.decodeSeed(seed); // throws if there is an issue - - assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); - - char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'O'); - - char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); - - byte[] data = "Connect Everything".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); - - assertTrue(theKey.verify(data, sig)); - - NKey otherKey = NKey.createOperator(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - - assertTrue(NKey.isValidPublicOperatorKey(publicKey)); - assertFalse(NKey.isValidPublicAccountKey(publicKey)); - assertFalse(NKey.isValidPublicClusterKey(publicKey)); - assertFalse(NKey.isValidPublicUserKey(publicKey)); - assertFalse(NKey.isValidPublicServerKey(publicKey)); - } - - @Test - public void testServer() throws Exception { - NKey theKey = NKey.createServer(null); - assertNotNull(theKey); - - char[] seed = theKey.getSeed(); - NKey.decodeSeed(seed); // throws if there is an issue - - assertEquals(NKey.fromSeed(theKey.getSeed()), NKey.fromSeed(theKey.getSeed())); - - char[] publicKey = theKey.getPublicKey(); - assertEquals(publicKey[0], 'N'); - - char[] privateKey = theKey.getPrivateKey(); - assertEquals(privateKey[0], 'P'); - - byte[] data = "Polaris and Pluto".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); - - assertTrue(theKey.verify(data, sig)); - - NKey otherKey = NKey.createServer(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - - assertTrue(NKey.isValidPublicServerKey(publicKey)); - assertFalse(NKey.isValidPublicAccountKey(publicKey)); - assertFalse(NKey.isValidPublicClusterKey(publicKey)); - assertFalse(NKey.isValidPublicOperatorKey(publicKey)); - assertFalse(NKey.isValidPublicUserKey(publicKey)); - } - - @Test - public void testPublicOnly() throws Exception { - NKey theKey = NKey.createUser(null); - assertNotNull(theKey); - - char[] publicKey = theKey.getPublicKey(); - - assertEquals(NKey.fromPublicKey(publicKey), NKey.fromPublicKey(publicKey)); - assertEquals(NKey.fromPublicKey(publicKey).hashCode(), NKey.fromPublicKey(publicKey).hashCode()); - - NKey pubOnly = NKey.fromPublicKey(publicKey); - - assertEquals(pubOnly, pubOnly); // for coverage - - byte[] data = "Public and Private".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertTrue(pubOnly.verify(data, sig)); - - NKey otherKey = NKey.createServer(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - assertNotEquals(otherKey, pubOnly); - - assertNotEquals(pubOnly.getPublicKey()[0], '\0'); - pubOnly.clear(); - assertEquals(pubOnly.getPublicKey()[0], '\0'); - } - - @Test - public void testPublicOnlyCantSign() { - assertThrows(IllegalStateException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); - - byte[] data = "Public and Private".getBytes(StandardCharsets.UTF_8); - pubOnly.sign(data); - }); - } - - @Test - public void testPublicOnlyCantProvideSeed() { - assertThrows(IllegalStateException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); - pubOnly.getSeed(); - }); - } - - @Test - public void testPublicOnlyCantProvidePrivate() { - assertThrows(IllegalStateException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey pubOnly = NKey.fromPublicKey(theKey.getPublicKey()); - pubOnly.getPrivateKey(); - }); - } - - @Test - public void testPublicFromSeedShouldFail() { - assertThrows(IllegalArgumentException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey.fromPublicKey(theKey.getSeed()); - }); - } - - @Test - public void testSeedFromPublicShouldFail() { - assertThrows(IllegalArgumentException.class, () -> { - NKey theKey = NKey.createUser(null); - NKey.fromSeed(theKey.getPublicKey()); - }); - } - - @Test - public void testFromSeed() throws Exception { - NKey theKey = NKey.createAccount(null); - assertNotNull(theKey); - - char[] seed = theKey.getSeed(); - assertEquals(NKey.fromSeed(seed), NKey.fromSeed(seed)); - assertEquals(NKey.fromSeed(seed).hashCode(), NKey.fromSeed(seed).hashCode()); - assertTrue(Arrays.equals(NKey.fromSeed(seed).getPublicKey(), NKey.fromSeed(seed).getPublicKey())); - assertTrue(Arrays.equals(NKey.fromSeed(seed).getPrivateKey(), NKey.fromSeed(seed).getPrivateKey())); - - assertTrue(seed[0] == 'S' && seed[1] == 'A'); - - NKey fromSeed = NKey.fromSeed(seed); - - byte[] data = "Seeds into trees".getBytes(StandardCharsets.UTF_8); - byte[] sig = theKey.sign(data); - - assertTrue(fromSeed.verify(data, sig)); - - NKey otherKey = NKey.createServer(null); - assertFalse(otherKey.verify(data, sig)); - assertNotEquals(otherKey, theKey); - assertNotEquals(otherKey, fromSeed); - } - - @Test - public void testFromBadSeed() { - assertThrows(IllegalArgumentException.class, () -> NKey.fromSeed("BadSeed".toCharArray())); - } - - @Test - public void testFromBadPublicKey() { - assertThrows(IllegalArgumentException.class, () -> NKey.fromPublicKey("BadSeed".toCharArray())); - } - - @Test - public void testBigSignVerify() throws Exception { - NKey theKey = NKey.createAccount(null); - assertNotNull(theKey); - - byte[] data = Files.readAllBytes(Paths.get("src/test/resources/keystore.jks")); - byte[] sig = theKey.sign(data); - - assertEquals(sig.length, ED25519_SIGNATURE_SIZE); - assertTrue(theKey.verify(data, sig)); - - char[] publicKey = theKey.getPublicKey(); - assertTrue(NKey.fromPublicKey(publicKey).verify(data, sig)); - - NKey otherKey = NKey.createUser(null); - byte[] sig2 = otherKey.sign(data); - - assertFalse(otherKey.verify(data, sig)); - assertFalse(Arrays.equals(sig2, sig)); - assertTrue(otherKey.verify(data, sig2)); - } - - /* - Compatibility/Interop data created from the following go code: - user, _ := nkeys.CreateUser(nil) - seed, _ := user.Seed() - publicKey, _ := user.PublicKey() - privateKey, _ := user.PrivateKey() - - data := []byte("Hello World") - sig, _ := user.Sign(data) - encSig := base64.URLEncoding.EncodeToString(sig) - - fmt.Printf("Seed: %q\n", seed) - fmt.Printf("Public: %q\n", publicKey) - fmt.Printf("Private: %q\n", privateKey) - - fmt.Printf("Data: %q\n", data) - fmt.Printf("Signature: %q\n", encSig) - */ - @Test - public void testInterop() throws Exception { - char[] seed = "SUAOXETHU4AZD2424VFDTDJ4TOEUSGZIXMRS6F3MSCMHUUORYHNEVM6ADE".toCharArray(); - char[] publicKey = "UB2YRJYJEFC5GZA5I47TCYYBIXQRAUA6B3MC4SR2WTXNUX6MTYM6BTBP".toCharArray(); - char[] privateKey = "PDVZEZ5HAGI6XGXFJI4Y2PE3RFERWKF3EMXRO3EQTB5FDUOB3JFLG5MIU4ESCROTMQOUOPZRMMAULYIQKAPA5WBOJI5LJ3W2L7GJ4GPAINHQ".toCharArray(); - String encodedSig = "dMSvD2P1Fm6knQGdMwz5h41aPYIOiPqwR-a3b7UNVJr4FcEfFoAIRbm_gtvLGIpplHTc7sZnSMeaS3Ogm1W_CA"; - String nonce = "UkY0TGZNbEVianJZY09F"; - String nonceEncodedSig = "ZNNvu8FDPhpVlyIqjfZGnLCmoAUQggdfdvhGtWLy29AM9TSa6_j15J2iph37j6_FvkGdd1v3crDANwHCqJuQCw"; - byte[] data = "Hello World".getBytes(StandardCharsets.UTF_8); - NKey fromSeed = NKey.fromSeed(seed); - NKey fromPublicKey = NKey.fromPublicKey(publicKey); - - assertEquals(fromSeed.getType(), NKey.Type.USER); - - byte[] nonceData = Base64.getUrlDecoder().decode(nonce); - byte[] nonceSig = Base64.getUrlDecoder().decode(nonceEncodedSig); - byte[] seedNonceSig = fromSeed.sign(nonceData); - String encodedSeedNonceSig = Base64.getUrlEncoder().withoutPadding().encodeToString(seedNonceSig); - - assertTrue(Arrays.equals(seedNonceSig, nonceSig)); - assertEquals(nonceEncodedSig, encodedSeedNonceSig); - - assertTrue(fromSeed.verify(nonceData, nonceSig)); - assertTrue(fromPublicKey.verify(nonceData, nonceSig)); - assertTrue(fromSeed.verify(nonceData, seedNonceSig)); - assertTrue(fromPublicKey.verify(nonceData, seedNonceSig)); - - byte[] seedSig = fromSeed.sign(data); - byte[] sig = Base64.getUrlDecoder().decode(encodedSig); - String encodedSeedSig = Base64.getUrlEncoder().withoutPadding().encodeToString(seedSig); - - assertTrue(Arrays.equals(seedSig, sig)); - assertEquals(encodedSig, encodedSeedSig); - - assertTrue(fromSeed.verify(data, sig)); - assertTrue(fromPublicKey.verify(data, sig)); - assertTrue(fromSeed.verify(data, seedSig)); - assertTrue(fromPublicKey.verify(data, seedSig)); - - // Make sure generation is the same - assertTrue(Arrays.equals(fromSeed.getSeed(), seed)); - assertTrue(Arrays.equals(fromSeed.getPublicKey(), publicKey)); - assertTrue(Arrays.equals(fromSeed.getPrivateKey(), privateKey)); - - DecodedSeed decoded = NKey.decodeSeed(seed); - char[] encodedSeed = NKey.encodeSeed(NKey.Type.fromPrefix(decoded.prefix), decoded.bytes); - assertTrue(Arrays.equals(encodedSeed, seed)); - } - - @Test - public void testTypeEnum() { - assertEquals(NKey.Type.USER, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_USER)); - assertEquals(NKey.Type.ACCOUNT, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_ACCOUNT)); - assertEquals(NKey.Type.SERVER, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_SERVER)); - assertEquals(NKey.Type.OPERATOR, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_OPERATOR)); - assertEquals(NKey.Type.CLUSTER, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_CLUSTER)); - assertEquals(NKey.Type.ACCOUNT, NKey.Type.fromPrefix(NKey.PREFIX_BYTE_PRIVATE)); - assertThrows(IllegalArgumentException.class, () -> { NKey.Type ignored = NKey.Type.fromPrefix(9999); }); - } - - @Test - public void testRemovePaddingAndClear() { - char[] withPad = "!".toCharArray(); - char[] removed = removePaddingAndClear(withPad); - assertEquals(withPad.length, removed.length); - assertEquals('!', removed[0]); - - withPad = "a=".toCharArray(); - removed = removePaddingAndClear(withPad); - assertEquals(1, removed.length); - assertEquals('a', removed[0]); - } - - @Test - public void testEquals() throws Exception { - NKey key = NKey.createServer(null); - assertEquals(key, key); - assertEquals(key, NKey.fromSeed(key.getSeed())); - assertNotEquals(key, new Object()); - assertNotEquals(key, NKey.createServer(null)); - assertNotEquals(key, NKey.createAccount(null)); - } - - @Test - public void testClear() throws Exception { - assertThrows(IllegalArgumentException.class, () -> { - NKey key = NKey.createServer(null); - key.clear(); - key.getPrivateKey(); - - }, "Invalid encoding"); - } -} diff --git a/src/test/java/io/nats/client/api/AccountStatisticsTests.java b/src/test/java/io/nats/client/api/AccountStatisticsTests.java index a1cb3a4bb..5af546d92 100644 --- a/src/test/java/io/nats/client/api/AccountStatisticsTests.java +++ b/src/test/java/io/nats/client/api/AccountStatisticsTests.java @@ -18,7 +18,6 @@ import java.util.Map; -import static io.nats.client.support.JsonUtils.EMPTY_JSON; import static io.nats.client.utils.ResourceUtils.dataAsString; import static org.junit.jupiter.api.Assertions.*; @@ -59,7 +58,7 @@ public void testAccountStatsImpl() { assertNotNull(as.toString()); // COVERAGE - as = new AccountStatistics(getDataMessage(EMPTY_JSON)); + as = new AccountStatistics(getDataMessage("{}")); assertEquals(0, as.getMemory()); assertEquals(0, as.getStorage()); assertEquals(0, as.getStreams()); diff --git a/src/test/java/io/nats/client/impl/JetStreamPullTests.java b/src/test/java/io/nats/client/impl/JetStreamPullTests.java index 5e62e3b45..2ee176ee9 100644 --- a/src/test/java/io/nats/client/impl/JetStreamPullTests.java +++ b/src/test/java/io/nats/client/impl/JetStreamPullTests.java @@ -16,7 +16,6 @@ import io.nats.client.*; import io.nats.client.api.AckPolicy; import io.nats.client.api.ConsumerConfiguration; -import io.nats.client.support.JsonUtils; import io.nats.client.support.Status; import io.nats.client.utils.TestBase; import org.junit.jupiter.api.Disabled; @@ -32,6 +31,7 @@ import static io.nats.client.api.ConsumerConfiguration.builder; import static io.nats.client.support.ApiConstants.*; +import static io.nats.client.support.JsonWriteUtils.*; import static io.nats.client.support.Status.*; import static org.junit.jupiter.api.Assertions.*; @@ -969,11 +969,11 @@ public BadPullRequestOptions() { @Override public String toJson() { - StringBuilder sb = JsonUtils.beginJson(); - JsonUtils.addField(sb, BATCH, 1); - JsonUtils.addFldWhenTrue(sb, NO_WAIT, true); - JsonUtils.addFieldAsNanos(sb, IDLE_HEARTBEAT, Duration.ofMillis(1)); - return JsonUtils.endJson(sb).toString(); + StringBuilder sb = beginJson(); + addField(sb, BATCH, 1); + addFldWhenTrue(sb, NO_WAIT, true); + addFieldAsNanos(sb, IDLE_HEARTBEAT, Duration.ofMillis(1)); + return endJson(sb).toString(); } } diff --git a/src/test/java/io/nats/client/impl/ListRequestsTests.java b/src/test/java/io/nats/client/impl/ListRequestsTests.java index dc94cd70c..8507fdbc7 100644 --- a/src/test/java/io/nats/client/impl/ListRequestsTests.java +++ b/src/test/java/io/nats/client/impl/ListRequestsTests.java @@ -22,7 +22,6 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; -import static io.nats.client.support.JsonUtils.EMPTY_JSON; import static io.nats.client.utils.ResourceUtils.dataAsString; import static org.junit.jupiter.api.Assertions.*; @@ -74,7 +73,7 @@ public void testConsumerListResponse() throws Exception { assertEquals(DateTimeUtils.parseDateTime("2022-06-29T20:33:21.163377Z"), sinfo.getLastActive()); clr = new ConsumerListReader(); - clr.process(getDataMessage(EMPTY_JSON)); + clr.process(getDataMessage("{}")); assertEquals(0, clr.getConsumers().size()); } diff --git a/src/test/java/io/nats/client/support/DateTimeUtilsTests.java b/src/test/java/io/nats/client/support/DateTimeUtilsTests.java deleted file mode 100644 index 27e2520ac..000000000 --- a/src/test/java/io/nats/client/support/DateTimeUtilsTests.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2015-2018 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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 org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import static org.junit.jupiter.api.Assertions.*; - -public final class DateTimeUtilsTests { - - @Test - public void testParseDateTime() { - assertEquals(1611186068, DateTimeUtils.parseDateTime("2021-01-20T23:41:08.579594Z").toEpochSecond()); - assertEquals(1612293508, DateTimeUtils.parseDateTime("2021-02-02T11:18:28.347722551-08:00").toEpochSecond()); - assertEquals(DateTimeUtils.DEFAULT_TIME, DateTimeUtils.parseDateTime("anything-not-valid")); - - ZonedDateTime zdt1 = DateTimeUtils.parseDateTime("2021-01-20T18:41:08-05:00"); - ZonedDateTime zdt2 = DateTimeUtils.parseDateTime("2021-01-20T23:41:08.000000Z"); - assertEquals(zdt1, zdt2); - - zdt1 = ZonedDateTime.of(2012, 1, 12, 6, 30, 1, 500, DateTimeUtils.ZONE_ID_GMT); - assertEquals(zdt1.toEpochSecond(), DateTimeUtils.parseDateTime("2012-01-12T06:30:01.000000500Z").toEpochSecond()); - } - - @Test - public void testToRfc3339() { - Instant i = Instant.ofEpochSecond(1611186068); - ZonedDateTime zdt1 = ZonedDateTime.ofInstant(i, ZoneId.systemDefault()); - ZonedDateTime zdt2 = ZonedDateTime.ofInstant(i, DateTimeUtils.ZONE_ID_GMT); - System.out.println(zdt1); - System.out.println(zdt2); - assertEquals(zdt1.toEpochSecond(), zdt2.toEpochSecond()); - - String rfc1 = DateTimeUtils.toRfc3339(zdt1); - String rfc2 = DateTimeUtils.toRfc3339(zdt2); - assertEquals(rfc1, rfc2); - System.out.println(zdt2.toEpochSecond()); - - assertEquals("2021-01-20T23:41:08.579594000Z", DateTimeUtils.toRfc3339(DateTimeUtils.parseDateTime("2021-01-20T23:41:08.579594Z"))); - assertEquals("2021-02-02T19:18:28.347722551Z", DateTimeUtils.toRfc3339(DateTimeUtils.parseDateTime("2021-02-02T11:18:28.347722551-08:00"))); - } - - @Test - public void testFromNow() { - long now = Instant.now().toEpochMilli(); - long then = Instant.from(DateTimeUtils.fromNow(5000)).toEpochMilli(); - assertTrue(then - now < 5050); // it takes about 10 ms to execute fromNow - - now = Instant.now().toEpochMilli(); - then = Instant.from(DateTimeUtils.fromNow(Duration.ofMillis(5000))).toEpochMilli(); - assertTrue(then - now < 5050); - } - - @Test - public void testEquals() { - Instant i = Instant.ofEpochSecond(System.currentTimeMillis()); - ZonedDateTime zdt1 = ZonedDateTime.ofInstant(i, ZoneId.of("America/New_York")); - ZonedDateTime zdt2 = ZonedDateTime.ofInstant(i, DateTimeUtils.ZONE_ID_GMT); - assertTrue(DateTimeUtils.equals(zdt1, zdt1)); - assertTrue(DateTimeUtils.equals(zdt1, zdt2)); - assertFalse(DateTimeUtils.equals(zdt1, null)); - assertFalse(DateTimeUtils.equals(null, zdt2)); - - i = Instant.ofEpochSecond(System.currentTimeMillis() - (1000 * 60 * 60 * 24)); - ZonedDateTime zdt3 = ZonedDateTime.ofInstant(i, ZoneId.of("America/New_York")); - ZonedDateTime zdt4 = ZonedDateTime.ofInstant(i, DateTimeUtils.ZONE_ID_GMT); - assertFalse(DateTimeUtils.equals(zdt3, zdt1)); - assertFalse(DateTimeUtils.equals(zdt4, zdt1)); - } -} diff --git a/src/test/java/io/nats/client/support/EncodingTests.java b/src/test/java/io/nats/client/support/EncodingTests.java index fce7467c5..d5e4a185f 100644 --- a/src/test/java/io/nats/client/support/EncodingTests.java +++ b/src/test/java/io/nats/client/support/EncodingTests.java @@ -17,9 +17,12 @@ import java.util.List; +import static io.nats.client.NKeyUtils.base32Decode; +import static io.nats.client.NKeyUtils.base32Encode; import static io.nats.client.support.Encoding.jsonDecode; import static io.nats.client.support.Encoding.jsonEncode; import static io.nats.client.utils.ResourceUtils.dataAsLines; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; public final class EncodingTests { @@ -67,5 +70,10 @@ private void _testEncodeDecode(String encodedInput, String targetDecode, String else { assertEquals(targetEncode, encoded); } + + byte[] testBytes = decoded.getBytes(); + char[] e32 = base32Encode(testBytes); + byte[] d32 = base32Decode(e32); + assertArrayEquals(testBytes, d32); } } diff --git a/src/test/java/io/nats/client/support/JsonParsingTests.java b/src/test/java/io/nats/client/support/JsonParsingTests.java deleted file mode 100644 index fa0795ff8..000000000 --- a/src/test/java/io/nats/client/support/JsonParsingTests.java +++ /dev/null @@ -1,950 +0,0 @@ -// Copyright 2023 The NATS Authors -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at: -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES 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 nl.jqno.equalsverifier.EqualsVerifier; -import nl.jqno.equalsverifier.Warning; -import org.junit.jupiter.api.Test; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.time.DateTimeException; -import java.time.Duration; -import java.time.ZonedDateTime; -import java.util.*; - -import static io.nats.client.support.Encoding.jsonEncode; -import static io.nats.client.support.JsonParser.*; -import static io.nats.client.support.JsonParser.Option.KEEP_NULLS; -import static io.nats.client.support.JsonValueUtils.*; -import static io.nats.client.utils.ResourceUtils.dataAsLines; -import static io.nats.client.utils.TestBase.*; -import static org.junit.jupiter.api.Assertions.*; - -public final class JsonParsingTests { - - @Test - public void testStringParsing() { - List encodeds = new ArrayList<>(); - List decodeds = new ArrayList<>(); - Map oMap = new HashMap<>(); - List list = new ArrayList<>(); - - int x = 0; - addField(key(x++), "b4\\after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4/after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\"after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\tafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\bafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\fafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\nafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\rafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4\\tafter", oMap, list, encodeds, decodeds); - addField(key(x++), "b4" + (char) 0 + "after", oMap, list, encodeds, decodeds); - addField(key(x++), "b4" + (char) 1 + "after", oMap, list, encodeds, decodeds); - - List utfs = dataAsLines("utf8-only-no-ws-test-strings.txt"); - for (String u : utfs) { - String uu = "b4\b\f\n\r\t" + u + "after"; - addField(key(x++), uu, oMap, list, encodeds, decodeds); - } - - addField(key(x++), PLAIN, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_SPACE, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_PRINTABLE, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_DOT, oMap, list, encodeds, decodeds); - addField(key(x++), STAR_NOT_SEGMENT, oMap, list, encodeds, decodeds); - addField(key(x++), GT_NOT_SEGMENT, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_DASH, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_UNDER, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_DOLLAR, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_LOW, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_127, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_FWD_SLASH, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_BACK_SLASH, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_EQUALS, oMap, list, encodeds, decodeds); - addField(key(x++), HAS_TIC, oMap, list, encodeds, decodeds); - - for (int i = 0; i < list.size(); i++) { - JsonValue v = list.get(i); - assertEquals(decodeds.get(i), v.string); - assertEquals(v.toJson(), "\"" + encodeds.get(i) + "\""); - } - } - - private void addField(String name, String decoded, - Map map, List list, - List encodeds, List decodeds) { - String enc = jsonEncode(decoded); - encodeds.add(enc); - decodeds.add(decoded); - JsonValue jv = new JsonValue(decoded); - map.put(name, jv); - list.add(jv); - } - - @SuppressWarnings("UnnecessaryUnicodeEscape") - @Test - public void testJsonValuePrimitives() throws JsonParseException { - Map oMap = new HashMap<>(); - oMap.put("trueKey1", new JsonValue(true)); - oMap.put("trueKey2", new JsonValue(Boolean.TRUE)); - oMap.put("falseKey1", new JsonValue(false)); - oMap.put("falseKey2", new JsonValue(Boolean.FALSE)); - oMap.put("stringKey", new JsonValue("hello world!")); - oMap.put("escapeStringKey", new JsonValue("h\be\tllo w\u1234orld!")); - oMap.put("nullKey", JsonValue.NULL); - oMap.put("intKey1", new JsonValue(Integer.MAX_VALUE)); - oMap.put("intKey2", new JsonValue(Integer.MIN_VALUE)); - oMap.put("longKey1", new JsonValue(Long.MAX_VALUE)); - oMap.put("longKey2", new JsonValue(Long.MIN_VALUE)); - oMap.put("doubleKey1", new JsonValue(Double.MAX_VALUE)); - oMap.put("doubleKey2", new JsonValue(Double.MIN_VALUE)); - oMap.put("floatKey1", new JsonValue(Float.MAX_VALUE)); - oMap.put("floatKey2", new JsonValue(Float.MIN_VALUE)); - oMap.put("bigDecimalKey1", new JsonValue(new BigDecimal("9223372036854775807.123"))); - oMap.put("bigDecimalKey2", new JsonValue(new BigDecimal("-9223372036854775808.123"))); - oMap.put("bigIntegerKey1", new JsonValue(new BigInteger("9223372036854775807"))); - oMap.put("bigIntegerKey2", new JsonValue(new BigInteger("-9223372036854775808"))); - - // some coverage here - JsonValue vMap = new JsonValue(oMap); - assertEquals(vMap.toJson(), vMap.toString()); - - validateMapTypes(oMap, oMap, true); - - // don't keep nulls - JsonValue parsed = parse(new JsonValue(oMap).toJson()); - assertNotNull(parsed.map); - assertEquals(oMap.size() - 1, parsed.map.size()); - validateMapTypes(parsed.map, oMap, false); - - // keep nulls - parsed = parse(new JsonValue(oMap).toJson(), KEEP_NULLS); - assertNotNull(parsed.map); - assertEquals(oMap.size(), parsed.map.size()); - validateMapTypes(parsed.map, oMap, true); - } - - private static void validateMapTypes(Map map, Map oMap, boolean original) { - assertEquals(JsonValue.Type.BOOL, map.get("trueKey1").type); - assertEquals(JsonValue.Type.BOOL, map.get("trueKey2").type); - assertEquals(JsonValue.Type.BOOL, map.get("falseKey1").type); - assertEquals(JsonValue.Type.BOOL, map.get("falseKey2").type); - assertEquals(JsonValue.Type.STRING, map.get("stringKey").type); - assertEquals(JsonValue.Type.STRING, map.get("escapeStringKey").type); - assertEquals(JsonValue.Type.INTEGER, map.get("intKey1").type); - assertEquals(JsonValue.Type.INTEGER, map.get("intKey2").type); - assertEquals(JsonValue.Type.LONG, map.get("longKey1").type); - assertEquals(JsonValue.Type.LONG, map.get("longKey2").type); - - assertNotNull(map.get("trueKey1").bool); - assertNotNull(map.get("trueKey2").bool); - assertNotNull(map.get("falseKey1").bool); - assertNotNull(map.get("falseKey2").bool); - assertNotNull(map.get("stringKey").string); - assertNotNull(map.get("escapeStringKey").string); - assertNotNull(map.get("intKey1").i); - assertNotNull(map.get("intKey2").i); - assertNotNull(map.get("longKey1").l); - assertNotNull(map.get("longKey2").l); - - assertEquals(oMap.get("trueKey1"), map.get("trueKey1")); - assertEquals(oMap.get("trueKey2"), map.get("trueKey2")); - assertEquals(oMap.get("falseKey1"), map.get("falseKey1")); - assertEquals(oMap.get("falseKey2"), map.get("falseKey2")); - assertEquals(oMap.get("stringKey"), map.get("stringKey")); - assertEquals(oMap.get("escapeStringKey"), map.get("escapeStringKey")); - assertEquals(oMap.get("intKey1"), map.get("intKey1")); - assertEquals(oMap.get("intKey2"), map.get("intKey2")); - assertEquals(oMap.get("longKey1"), map.get("longKey1")); - assertEquals(oMap.get("longKey2"), map.get("longKey2")); - - if (original) { - assertNotNull(oMap.get("intKey1").i); - assertNotNull(oMap.get("intKey2").i); - assertNotNull(oMap.get("longKey1").l); - assertNotNull(oMap.get("longKey2").l); - assertNotNull(oMap.get("doubleKey1").d); - assertNotNull(oMap.get("doubleKey2").d); - assertNotNull(oMap.get("floatKey1").f); - assertNotNull(oMap.get("floatKey2").f); - assertNotNull(oMap.get("bigDecimalKey1").bd); - assertNotNull(oMap.get("bigDecimalKey2").bd); - assertNotNull(oMap.get("bigIntegerKey1").bi); - assertNotNull(oMap.get("bigIntegerKey2").bi); - - assertEquals(JsonValue.Type.NULL, map.get("nullKey").type); - assertNull(map.get("nullKey").object); - assertEquals(oMap.get("nullKey"), map.get("nullKey")); - } - else { - assertNotNull(oMap.get("intKey1").number); - assertNotNull(oMap.get("intKey2").number); - assertNotNull(oMap.get("longKey1").number); - assertNotNull(oMap.get("longKey2").number); - assertNotNull(oMap.get("doubleKey1").number); - assertNotNull(oMap.get("doubleKey2").number); - assertNotNull(oMap.get("floatKey1").number); - assertNotNull(oMap.get("floatKey2").number); - assertNotNull(oMap.get("bigDecimalKey1").number); - assertNotNull(oMap.get("bigDecimalKey2").number); - assertNotNull(oMap.get("bigIntegerKey1").number); - assertNotNull(oMap.get("bigIntegerKey2").number); - } - } - - @Test - public void testArray() throws JsonParseException { - List list = new ArrayList<>(); - list.add(new JsonValue("string")); - list.add(new JsonValue(true)); - list.add(JsonValue.NULL); - list.add(JsonValue.EMPTY_MAP); - list.add(JsonValue.EMPTY_ARRAY); - - JsonValue root = parse(new JsonValue(list).toJson()); - assertNotNull(root.array); - assertEquals(list.size(), root.array.size()); - List array = root.array; - for (int i = 0; i < array.size(); i++) { - JsonValue v = array.get(i); - JsonValue p = root.array.get(i); - assertEquals(v.object, p.object); - assertTrue(list.contains(v)); - } - - - list.clear(); - list.add(new JsonValue(1)); - list.add(new JsonValue(Long.MAX_VALUE)); - list.add(new JsonValue(Double.MAX_VALUE)); - list.add(new JsonValue(Float.MAX_VALUE)); - list.add(new JsonValue(new BigDecimal(Double.toString(Double.MAX_VALUE)))); - list.add(new JsonValue(new BigInteger(Long.toString(Long.MAX_VALUE)))); - - root = parse(new JsonValue(list).toJson()); - assertNotNull(root.array); - assertEquals(list.size(), root.array.size()); - array = root.array; - for (int i = 0; i < array.size(); i++) { - JsonValue v = array.get(i); - JsonValue p = root.array.get(i); - assertEquals(v.object, p.object); - assertEquals(v.number, p.number); - } - - Map rootMap = new HashMap<>(); - rootMap.put("list", new JsonValue(list)); - rootMap.put("array", new JsonValue(list.toArray(new JsonValue[0]))); - root = new JsonValue(rootMap); - List mappedList = readValue(root, "list").array; - - List mappedList2 = parse(new JsonValue(mappedList).toJson()).array; - List mappedArray = readValue(root, "array").array; - List mappedArray2 = parse(new JsonValue(list.toArray(new JsonValue[0])).toJson()).array; - for (int i = 0; i < list.size(); i++) { - JsonValue v = list.get(i); - JsonValue lv = mappedList.get(i); - JsonValue lv2 = mappedList2.get(i); - JsonValue av = mappedArray.get(i); - JsonValue av2 = mappedArray2.get(i); - assertNotNull(lv); - assertNotNull(lv2); - assertNotNull(av); - assertNotNull(av2); - assertEquals(v, lv); - assertEquals(v, av); - - // conversions are not perfect for doubles and floats, but that's a java thing, not a parser thing - if (v.type == lv2.type) { - assertEquals(v, lv2); - } - if (v.type == av2.type) { - assertEquals(v, av2); - } - } - } - - @Test - public void testListReading() { - List jvList = new ArrayList<>(); - jvList.add(new JsonValue("string1")); - jvList.add(new JsonValue("string2")); - jvList.add(new JsonValue("")); - jvList.add(new JsonValue(true)); - jvList.add(new JsonValue((String)null)); - jvList.add(JsonValue.NULL); - jvList.add(JsonValue.EMPTY_MAP); - jvList.add(JsonValue.EMPTY_ARRAY); - jvList.add(new JsonValue(Integer.MAX_VALUE)); - jvList.add(new JsonValue(Long.MAX_VALUE)); - Map jvMap = new HashMap<>(); - jvMap.put("list", new JsonValue(jvList)); - JsonValue root = new JsonValue(jvMap); - - List list = readStringList(root, "list"); - assertEquals(3, list.size()); - assertTrue(list.contains("string1")); - assertTrue(list.contains("string2")); - assertTrue(list.contains("")); - - list = readStringListIgnoreEmpty(root, "list"); - assertEquals(2, list.size()); - assertTrue(list.contains("string1")); - assertTrue(list.contains("string2")); - - jvList.remove(0); - jvList.remove(0); - jvList.remove(0); - list = readOptionalStringList(root, "list"); - assertNull(list); - - list = readOptionalStringList(root, "na"); - assertNull(list); - - jvList.clear(); - Duration d0 = Duration.ofNanos(10000000000L); - Duration d1 = Duration.ofNanos(20000000000L); - Duration d2 = Duration.ofNanos(30000000000L); - - jvList.add(instance(d0)); - jvList.add(instance(d1)); - jvList.add(instance(d2)); - jvList.add(new JsonValue("not duration nanos")); - - root = new JsonValue(jvMap); - - List dlist = readNanosList(root, "list"); - assertEquals(3, dlist.size()); - assertEquals(d0, dlist.get(0)); - assertEquals(d1, dlist.get(1)); - assertEquals(d2, dlist.get(2)); - } - - @Test - public void testGetIntLong() { - 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); - assertEquals(Integer.MAX_VALUE, getInteger(i)); - assertEquals(Integer.MAX_VALUE, getInteger(li)); - assertNull(getInteger(lmax)); - assertNull(getInteger(lmin)); - assertNull(getInteger(JsonValue.NULL)); - assertNull(getInteger(JsonValue.EMPTY_MAP)); - assertNull(getInteger(JsonValue.EMPTY_ARRAY)); - - assertEquals(Integer.MAX_VALUE, getLong(i)); - assertEquals(Integer.MAX_VALUE, getLong(li)); - assertEquals(Long.MAX_VALUE, getLong(lmax)); - assertEquals(Long.MIN_VALUE, getLong(lmin)); - assertNull(getLong(JsonValue.NULL)); - assertNull(getLong(JsonValue.EMPTY_MAP)); - assertNull(getLong(JsonValue.EMPTY_ARRAY)); - - assertEquals(Integer.MAX_VALUE, getLong(i, -1)); - assertEquals(Integer.MAX_VALUE, getLong(li, -1)); - assertEquals(Long.MAX_VALUE, getLong(lmax, -1)); - assertEquals(Long.MIN_VALUE, getLong(lmin, -1)); - assertEquals(-1, getLong(JsonValue.NULL, -1)); - assertEquals(-1, getLong(JsonValue.EMPTY_MAP, -1)); - assertEquals(-1, getLong(JsonValue.EMPTY_ARRAY, -1)); - } - - @Test - public void testConstantsAreReadOnly() { - assertThrows(UnsupportedOperationException.class, () -> JsonValue.EMPTY_MAP.map.put("foo", null)); - assertThrows(UnsupportedOperationException.class, () -> JsonValue.EMPTY_ARRAY.array.add(null)); - } - - @Test - public void testNullJsonValue() { - assertEquals(JsonValue.Type.NULL, JsonValue.NULL.type); - assertNull(JsonValue.NULL.object); - assertNull(JsonValue.NULL.map); - assertNull(JsonValue.NULL.array); - assertNull(JsonValue.NULL.string); - assertNull(JsonValue.NULL.bool); - assertNull(JsonValue.NULL.number); - assertNull(JsonValue.NULL.i); - assertNull(JsonValue.NULL.l); - assertNull(JsonValue.NULL.d); - assertNull(JsonValue.NULL.f); - assertNull(JsonValue.NULL.bd); - assertNull(JsonValue.NULL.bi); - assertEquals(JsonValue.NULL, new JsonValue((String)null)); - assertEquals(JsonValue.NULL, new JsonValue((Boolean) null)); - assertEquals(JsonValue.NULL, new JsonValue((Map)null)); - assertEquals(JsonValue.NULL, new JsonValue((List)null)); - assertEquals(JsonValue.NULL, new JsonValue((JsonValue[])null)); - assertEquals(JsonValue.NULL, new JsonValue((BigDecimal)null)); - assertEquals(JsonValue.NULL, new JsonValue((BigInteger) null)); - } - - @Test - public void testGetMapped() { - ZonedDateTime zdt = DateTimeUtils.gmtNow(); - Duration dur = Duration.ofNanos(4273); - Duration dur2 = Duration.ofNanos(7342); - - JsonValue v = new JsonValue(new HashMap<>()); - v.map.put("bool", new JsonValue(Boolean.TRUE)); - v.map.put("string", new JsonValue("hello")); - v.map.put("int", new JsonValue(Integer.MAX_VALUE)); - v.map.put("long", new JsonValue(Long.MAX_VALUE)); - v.map.put("date", new JsonValue(DateTimeUtils.toRfc3339(zdt))); - v.map.put("dur", new JsonValue(dur.toNanos())); - v.map.put("strings", new JsonValue(new JsonValue[]{new JsonValue("s1"), new JsonValue("s2")})); - v.map.put("durs", new JsonValue(new JsonValue[]{new JsonValue(dur.toNanos()), new JsonValue(dur2.toNanos())})); - - assertNotNull(readValue(v, "string")); - assertNull(readValue(v, "na")); - assertEquals(JsonValue.EMPTY_MAP, readObject(v, "na")); - assertNull(read(null, "na", vv -> vv)); - assertNull(read(JsonValue.NULL, "na", vv -> vv)); - assertNull(read(JsonValue.EMPTY_MAP, "na", vv -> vv)); - - assertNull(readDate(null, "na")); - assertNull(readDate(JsonValue.NULL, "na")); - assertNull(readDate(JsonValue.EMPTY_MAP, "na")); - assertEquals(zdt, readDate(v, "date")); - assertNull(readDate(v, "int")); - - assertFalse(readBoolean(null, "na")); - assertFalse(readBoolean(null, "na", false)); - assertTrue(readBoolean(null, "na", true)); - assertFalse(readBoolean(JsonValue.NULL, "na")); - assertFalse(readBoolean(JsonValue.NULL, "na", false)); - assertTrue(readBoolean(JsonValue.NULL, "na", true)); - assertFalse(readBoolean(JsonValue.EMPTY_MAP, "na")); - assertFalse(readBoolean(JsonValue.EMPTY_MAP, "na", false)); - assertTrue(readBoolean(JsonValue.EMPTY_MAP, "na", true)); - assertFalse(readBoolean(v, "na")); - assertFalse(readBoolean(v, "na", false)); - assertTrue(readBoolean(v, "na", true)); - assertFalse(readBoolean(v, "int")); - assertFalse(readBoolean(v, "int", false)); - assertTrue(readBoolean(v, "int", true)); - - assertTrue(readBoolean(v, "bool")); - assertTrue(readBoolean(v, "bool", false)); - assertFalse(readBoolean(v, "na")); - assertFalse(readBoolean(v, "na", false)); - assertTrue(readBoolean(v, "na", true)); - - assertEquals("hello", readString(v, "string")); - assertEquals("hello", readString(v, "string", null)); - assertNull(readString(v, "na")); - assertNull(readString(v, "na", null)); - assertEquals("default", readString(v, "na", "default")); - assertNull(readString(JsonValue.NULL, "na")); - assertNull(readString(JsonValue.NULL, "na", null)); - assertEquals("default", readString(JsonValue.NULL, "na", "default")); - - assertEquals(zdt, readDate(v, "date")); - assertNull(readDate(v, "na")); - assertThrows(DateTimeException.class, () -> readDate(v, "string")); - - assertEquals(Integer.MAX_VALUE, readInteger(v, "int")); - assertEquals(Integer.MAX_VALUE, readInteger(v, "int", -1)); - assertNull(readInteger(v, "string")); - assertEquals(-1, readInteger(v, "string", -1)); - assertNull(readInteger(v, "na")); - assertEquals(-1, readInteger(v, "na", -1)); - - assertEquals(Long.MAX_VALUE, readLong(v, "long")); - assertEquals(Long.MAX_VALUE, readLong(v, "long", -1)); - assertNull(readLong(v, "string")); - assertEquals(-1, readLong(v, "string", -1)); - assertNull(readLong(v, "na")); - assertEquals(-1, readLong(v, "na", -1)); - - assertEquals(dur, readNanos(v, "dur")); - assertEquals(dur, readNanos(v, "dur", null)); - assertNull(readNanos(v, "string")); - assertNull(readNanos(v, "string", null)); - assertEquals(dur2, readNanos(v, "string", dur2)); - assertNull(readNanos(v, "na")); - assertNull(readNanos(v, "na", null)); - assertEquals(dur2, readNanos(v, "na", dur2)); - - // these aren't maps - JsonValue jvn = new JsonValue(1); - JsonValue jvs = new JsonValue("s"); - JsonValue jvb = new JsonValue(true); - JsonValue[] notMaps = new JsonValue[] {JsonValue.NULL, JsonValue.EMPTY_ARRAY, jvn, jvs, jvb}; - - for (JsonValue vv : notMaps) { - assertNull(readValue(vv, "na")); - assertEquals(JsonValue.EMPTY_MAP, readObject(vv, "na")); - assertNull(readDate(vv, "na")); - assertNull(readInteger(vv, "na")); - assertEquals(-1, readInteger(vv, "na", -1)); - assertNull(readLong(vv, "na")); - assertEquals(-2, readLong(vv, "na", -2)); - assertFalse(readBoolean(vv, "na")); - assertNull(readBoolean(vv, "na", null)); - assertTrue(readBoolean(vv, "na", true)); - assertFalse(readBoolean(vv, "na", false)); - assertNull(readNanos(vv, "na")); - assertEquals(Duration.ZERO, readNanos(vv, "na", Duration.ZERO)); - } - } - - @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(); - - assertEquals(expected, parse(json)); - assertEquals(expected, parse(json, 0)); - assertEquals(expected, parse(json, KEEP_NULLS)); - assertEquals(expected, parse(ca)); - assertEquals(expected, parse(ca, 0)); - assertEquals(expected, parse(ca, KEEP_NULLS)); - assertEquals(expected, parse(ba)); - assertEquals(expected, parse(ba, KEEP_NULLS)); - - assertEquals(expected, parseUnchecked(json)); - assertEquals(expected, parseUnchecked(json, 0)); - assertEquals(expected, parseUnchecked(json, KEEP_NULLS)); - assertEquals(expected, parseUnchecked(ca)); - assertEquals(expected, parseUnchecked(ca, 0)); - assertEquals(expected, parseUnchecked(ca, KEEP_NULLS)); - assertEquals(expected, parseUnchecked(ba)); - assertEquals(expected, parseUnchecked(ba, KEEP_NULLS)); - } - - @Test - public void testParsingCoverage() throws JsonParseException { - validateParse(JsonValue.NULL, ""); - validateParse(JsonValue.EMPTY_MAP, "{}"); - validateParse(JsonValue.EMPTY_ARRAY, "[]"); - - IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> parse("{}", -1)); - assertTrue(iae.getMessage().contains("Invalid start index.")); - - validateThrows("{", "Text must end with '}'"); - validateThrows("{{", "Cannot directly nest another Object or Array."); - validateThrows("{[", "Cannot directly nest another Object or Array."); - validateThrows("{\"foo\":1 ]", "Expected a ',' or '}'."); - validateThrows("{\"foo\" 1", "Expected a ':' after a key."); - validateThrows("[\"bad\",", "Unexpected end of data."); // missing close - validateThrows("[1Z]", "Invalid value."); - validateThrows("t", "Invalid value."); - validateThrows("f", "Invalid value."); - validateThrows("\"u", "Unterminated string."); - validateThrows("\"u\r", "Unterminated string."); - validateThrows("\"u\n", "Unterminated string."); - validateThrows("\"\\x\"", "Illegal escape."); - validateThrows("\"\\u000", "Illegal escape."); - validateThrows("\"\\uzzzz", "Illegal escape."); - - JsonValue v = JsonParser.parse((char[])null); - assertEquals(JsonValue.NULL, v); - - v = parse("{\"foo\":1,}"); - assertEquals(1, v.map.size()); - assertTrue(v.map.containsKey("foo")); - assertEquals(1, v.map.get("foo").i); - - v = parse("INFO{\"foo\":1,}", 4); - assertEquals(1, v.map.size()); - assertTrue(v.map.containsKey("foo")); - assertEquals(1, v.map.get("foo").i); - - v = parse("[\"foo\",]"); // handles dangling commas fine - assertEquals(1, v.array.size()); - assertEquals("foo", v.array.get(0).string); - - String s = "foo \b \t \n \f \r \" \\ /"; - String j = "\"" + Encoding.jsonEncode(s) + "\""; - v = parse(j); - assertNotNull(v.string); - assertEquals(s, v.string); - - // every constructor - String json = "{}"; - new JsonParser(json.toCharArray()); - new JsonParser(json.toCharArray(), Option.KEEP_NULLS); - parse(json.toCharArray()); - parse(json.toCharArray(), 0); - parse(json.toCharArray(), Option.KEEP_NULLS); - parse(json.toCharArray(), 0, Option.KEEP_NULLS); - parse(json); - parse(json, 0); - parse(json, Option.KEEP_NULLS); - parse(json, 0, Option.KEEP_NULLS); - parse(json.getBytes()); - parse(json.getBytes(), Option.KEEP_NULLS); - parseUnchecked(json.toCharArray()); - parseUnchecked(json.toCharArray(), 0); - parseUnchecked(json.toCharArray(), Option.KEEP_NULLS); - parseUnchecked(json.toCharArray(), 0, Option.KEEP_NULLS); - parseUnchecked(json); - parseUnchecked(json, 0); - parseUnchecked(json, Option.KEEP_NULLS); - parseUnchecked(json, 0, Option.KEEP_NULLS); - parseUnchecked(json.getBytes()); - parseUnchecked(json.getBytes(), Option.KEEP_NULLS); - } - - private void validateThrows(String json, String errorText) { - // also provides coverage for every constructor - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json.toCharArray()))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json.toCharArray(), 0))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json.toCharArray(), Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json.toCharArray(), 0, Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json, 0))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json, Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json, 0, Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json.getBytes()))); - validateThrowError(errorText, assertThrows(JsonParseException.class, () -> parse(json.getBytes(), Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json.toCharArray()))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json.toCharArray(), 0))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json.toCharArray(), Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json.toCharArray(), 0, Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json, 0))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json, Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json, 0, Option.KEEP_NULLS))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json.getBytes()))); - validateThrowError(errorText, assertThrows(RuntimeException.class, () -> parseUnchecked(json.getBytes(), Option.KEEP_NULLS))); - } - - private static void validateThrowError(String errorText, Exception e) { - assertTrue(e.getMessage().contains(errorText)); - } - - @Test - public void testNumberParsing() throws JsonParseException { - assertEquals(JsonValue.Type.INTEGER, parse("1").type); - assertEquals(JsonValue.Type.INTEGER, parse(Integer.toString(Integer.MAX_VALUE)).type); - assertEquals(JsonValue.Type.INTEGER, parse(Integer.toString(Integer.MIN_VALUE)).type); - assertEquals(JsonValue.Type.LONG, parse(Long.toString((long)Integer.MAX_VALUE + 1)).type); - assertEquals(JsonValue.Type.LONG, parse(Long.toString((long)Integer.MIN_VALUE - 1)).type); - assertEquals(JsonValue.Type.DOUBLE, parse("-0").type); - assertEquals(JsonValue.Type.DOUBLE, parse("-0.0").type); - assertEquals(JsonValue.Type.DOUBLE, parse("0.1d").type); - assertEquals(JsonValue.Type.DOUBLE, parse("0.f").type); - assertEquals(JsonValue.Type.DOUBLE, parse("0.1f").type); - assertEquals(JsonValue.Type.DOUBLE, parse("-0x1.fffp1").type); - assertEquals(JsonValue.Type.DOUBLE, parse("0x1.0P-1074").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("0.2").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("244273.456789012345").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("244273.456789012345").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("0.1234567890123456789").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("-24.42e7345").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("-24.42E7345").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("-.01").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, parse("00.001").type); - assertEquals(JsonValue.Type.BIG_INTEGER, parse("12345678901234567890").type); - - String str = new BigInteger( Long.toString(Long.MAX_VALUE) ).add( BigInteger.ONE ).toString(); - assertEquals(JsonValue.Type.BIG_INTEGER, parse(str).type); - - validateThrows("-0x123", "Invalid value."); - JsonParseException e; - - e = assertThrows(JsonParseException.class, () -> parse("-")); - assertTrue(e.getMessage().contains("Invalid value.")); - - e = assertThrows(JsonParseException.class, () -> parse("00")); - assertTrue(e.getMessage().contains("Invalid value.")); - - e = assertThrows(JsonParseException.class, () -> parse("NaN")); - assertTrue(e.getMessage().contains("Invalid value.")); - - e = assertThrows(JsonParseException.class, () -> parse("-NaN")); - assertTrue(e.getMessage().contains("Invalid value.")); - - e = assertThrows(JsonParseException.class, () -> parse("Infinity")); - assertTrue(e.getMessage().contains("Invalid value.")); - - e = assertThrows(JsonParseException.class, () -> parse("-Infinity")); - assertTrue(e.getMessage().contains("Invalid value.")); - } - - @Test - public void testValueUtilsInstanceDuration() { - JsonValue v = instance(Duration.ofSeconds(1)); - assertNotNull(v.l); - assertEquals(1000000000L, v.l); - } - - static class TestSerializableMap implements JsonSerializable { - @Override - public String toJson() { - JsonValue v = new JsonValue(new HashMap<>()); - v.map.put("a", new JsonValue("A")); - v.map.put("b", new JsonValue("B")); - v.map.put("c", new JsonValue("C")); - return v.toJson(); - } - } - - static class TestSerializableList implements JsonSerializable { - @Override - public String toJson() { - JsonValue v = new JsonValue(new ArrayList<>()); - v.array.add(new JsonValue("X")); - v.array.add(new JsonValue("Y")); - v.array.add(new JsonValue("Z")); - return v.toJson(); - } - } - - @Test - public void testValueUtilsInstanceList() { - List list = new ArrayList<>(); - list.add("Hello"); - list.add(""); - list.add('c'); - list.add(1); - list.add(1L); - list.add(1D); - list.add(1F); - list.add(new BigDecimal("1.0")); - list.add(new BigInteger("1")); - list.add(true); - list.add(new HashMap<>()); - list.add(new ArrayList<>()); - list.add(new HashSet<>()); - list.add(new TestSerializableMap()); - list.add(new TestSerializableList()); - list.add(null); - JsonValue v = instance(list); - assertNotNull(v.array); - assertEquals(16, v.array.size()); - assertEquals(JsonValue.Type.STRING, v.array.get(0).type); - assertEquals(JsonValue.Type.NULL, v.array.get(1).type); - assertEquals(JsonValue.Type.STRING, v.array.get(2).type); - assertEquals(JsonValue.Type.INTEGER, v.array.get(3).type); - assertEquals(JsonValue.Type.LONG, v.array.get(4).type); - assertEquals(JsonValue.Type.DOUBLE, v.array.get(5).type); - assertEquals(JsonValue.Type.FLOAT, v.array.get(6).type); - assertEquals(JsonValue.Type.BIG_DECIMAL, v.array.get(7).type); - assertEquals(JsonValue.Type.BIG_INTEGER, v.array.get(8).type); - assertEquals(JsonValue.Type.BOOL, v.array.get(9).type); - assertEquals(JsonValue.Type.MAP, v.array.get(10).type); - assertEquals(JsonValue.Type.ARRAY, v.array.get(11).type); - assertEquals(JsonValue.Type.ARRAY, v.array.get(12).type); - assertEquals(JsonValue.Type.MAP, v.array.get(13).type); - assertEquals(JsonValue.Type.ARRAY, v.array.get(14).type); - assertEquals(JsonValue.Type.NULL, v.array.get(15).type); - } - - @Test - public void testValueUtilsInstanceMap() { - Map map = new HashMap<>(); - map.put("string", "Hello"); - map.put("char", 'c'); - map.put("int", 1); - map.put("long", Long.MAX_VALUE); - map.put("double", 1D); - map.put("float", 1F); - map.put("bd", new BigDecimal("1.0")); - map.put("bi", new BigInteger(Long.toString(Long.MAX_VALUE))); - map.put("bool", true); - map.put("map", new HashMap<>()); - map.put("list", new ArrayList<>()); - map.put("set", new HashSet<>()); - map.put("smap", new TestSerializableMap()); - map.put("slist", new TestSerializableList()); - map.put("jv", JsonValue.EMPTY_MAP); - map.put("null", null); - map.put("jvNull", JsonValue.NULL); - map.put("empty_is_null", ""); - validateMap(true, false, instance(map)); - } - - @Test - public void testValueUtilsMapBuilder() { - MapBuilder builder = mapBuilder() - .put("string", "Hello") - .put("char", 'c') - .put("int", 1) - .put("long", Long.MAX_VALUE) - .put("double", 1D) - .put("float", 1F) - .put("bd", new BigDecimal("1.0")) - .put("bi", new BigInteger(Long.toString(Long.MAX_VALUE))) - .put("bool", true) - .put("map", new HashMap<>()) - .put("list", new ArrayList<>()) - .put("set", new HashSet<>()) - .put("smap", new TestSerializableMap()) - .put("slist", new TestSerializableList()) - .put("jv", JsonValue.EMPTY_MAP) - .put("null", null) - .put("jvNull", JsonValue.NULL) - .put("empty_is_null", ""); - validateMap(false, false, builder.toJsonValue()); - //noinspection deprecation - validateMap(false, false, builder.getJsonValue()); // coverage for deprecated - validateMap(false, true, JsonParser.parseUnchecked(builder.toJson())); - } - - private static void validateMap(boolean checkNull, boolean parsed, JsonValue v) { - assertNotNull(v.map); - assertEquals(JsonValue.Type.STRING, v.map.get("string").type); - assertEquals(JsonValue.Type.STRING, v.map.get("char").type); - assertEquals(JsonValue.Type.INTEGER, v.map.get("int").type); - assertEquals(JsonValue.Type.LONG, v.map.get("long").type); - if (parsed) { - assertEquals(JsonValue.Type.BIG_DECIMAL, v.map.get("double").type); - assertEquals(JsonValue.Type.BIG_DECIMAL, v.map.get("float").type); - assertEquals(JsonValue.Type.LONG, v.map.get("bi").type); - } - else { - assertEquals(JsonValue.Type.DOUBLE, v.map.get("double").type); - assertEquals(JsonValue.Type.FLOAT, v.map.get("float").type); - assertEquals(JsonValue.Type.BIG_INTEGER, v.map.get("bi").type); - } - assertEquals(JsonValue.Type.BIG_DECIMAL, v.map.get("bd").type); - assertEquals(JsonValue.Type.BOOL, v.map.get("bool").type); - assertEquals(JsonValue.Type.MAP, v.map.get("map").type); - assertEquals(JsonValue.Type.ARRAY, v.map.get("list").type); - assertEquals(JsonValue.Type.ARRAY, v.map.get("set").type); - assertEquals(JsonValue.Type.MAP, v.map.get("smap").type); - assertEquals(JsonValue.Type.ARRAY, v.map.get("slist").type); - assertEquals(JsonValue.Type.MAP, v.map.get("jv").type); - if (checkNull) { - assertEquals(18, v.map.size()); - assertEquals(JsonValue.Type.NULL, v.map.get("null").type); - assertEquals(JsonValue.Type.NULL, v.map.get("jvNull").type); - assertEquals(JsonValue.Type.NULL, v.map.get("empty_is_null").type); - } - else { - assertEquals(15, v.map.size()); - } - } - - @Test - public void testValueUtilsInstanceArray() { - List list = new ArrayList<>(); - list.add("Hello"); - list.add('c'); - list.add(1); - list.add(Long.MAX_VALUE); - list.add(1D); - list.add(1F); - list.add(new BigDecimal("1.0")); - list.add(new BigInteger(Long.toString(Long.MAX_VALUE))); - list.add(true); - list.add(new HashMap<>()); - list.add(new ArrayList<>()); - list.add(new TestSerializableMap()); - list.add(new TestSerializableList()); - list.add(JsonValue.EMPTY_MAP); - list.add(null); - list.add(JsonValue.NULL); - validateArray(true, false, instance(list)); - } - - @Test - public void testValueUtilsArrayBuilder() { - ArrayBuilder builder = arrayBuilder() - .add("Hello") - .add('c') - .add(1) - .add(Long.MAX_VALUE) - .add(1D) - .add(1F) - .add(new BigDecimal("1.0")) - .add(new BigInteger(Long.toString(Long.MAX_VALUE))) - .add(true) - .add(new HashMap<>()) - .add(new ArrayList<>()) - .add(new TestSerializableMap()) - .add(new TestSerializableList()) - .add(JsonValue.EMPTY_MAP) - .add(null) - .add(JsonValue.NULL); - validateArray(false, false, builder.toJsonValue()); - //noinspection deprecation - validateArray(false, false, builder.getJsonValue()); // coverage for deprecated - validateArray(false, true, JsonParser.parseUnchecked(builder.toJson())); - } - - private static void validateArray(boolean checkNull, boolean parsed, JsonValue v) { - assertNotNull(v.array); - assertEquals(JsonValue.Type.STRING, v.array.get(0).type); - assertEquals(JsonValue.Type.STRING, v.array.get(1).type); - assertEquals(JsonValue.Type.INTEGER, v.array.get(2).type); - assertEquals(JsonValue.Type.LONG, v.array.get(3).type); - if (parsed) { - assertEquals(JsonValue.Type.BIG_DECIMAL, v.array.get(4).type); - assertEquals(JsonValue.Type.BIG_DECIMAL, v.array.get(5).type); - assertEquals(JsonValue.Type.LONG, v.array.get(7).type); - } - else { - assertEquals(JsonValue.Type.DOUBLE, v.array.get(4).type); - assertEquals(JsonValue.Type.FLOAT, v.array.get(5).type); - assertEquals(JsonValue.Type.BIG_INTEGER, v.array.get(7).type); - } - assertEquals(JsonValue.Type.BIG_DECIMAL, v.array.get(6).type); - assertEquals(JsonValue.Type.BOOL, v.array.get(8).type); - assertEquals(JsonValue.Type.MAP, v.array.get(9).type); - assertEquals(JsonValue.Type.ARRAY, v.array.get(10).type); - assertEquals(JsonValue.Type.MAP, v.array.get(11).type); - assertEquals(JsonValue.Type.ARRAY, v.array.get(12).type); - assertEquals(JsonValue.Type.MAP, v.array.get(13).type); - if (checkNull) { - assertEquals(16, v.array.size()); - assertEquals(JsonValue.Type.NULL, v.array.get(14).type); - assertEquals(JsonValue.Type.NULL, v.array.get(15).type); - } - else { - assertEquals(14, v.array.size()); - } - } - - @Test - public void testReadStringStringMap() { - JsonValue jv = mapBuilder() - .put("stringString", mapBuilder().put("a", "A").put("b", "B").toJsonValue()) - .put("empty", new HashMap<>()) - .put("string", "string") - .toJsonValue(); - - assertNull(readStringStringMap(jv, "string")); - assertNull(readStringStringMap(jv, "empty")); - Map stringString = readStringStringMap(jv, "stringString"); - assertNotNull(stringString); - assertEquals(2, stringString.size()); - assertEquals("A", stringString.get("a")); - assertEquals("B", stringString.get("b")); - } -} diff --git a/src/test/java/io/nats/client/support/JwtUtilsTests.java b/src/test/java/io/nats/client/support/JwtUtilsTests.java index 32ca9114d..a33eb035a 100644 --- a/src/test/java/io/nats/client/support/JwtUtilsTests.java +++ b/src/test/java/io/nats/client/support/JwtUtilsTests.java @@ -322,11 +322,14 @@ public void testUserClaimJson() { assertEquals(FULL_JSON, uc.toJson()); } + @SuppressWarnings("deprecation") @Test public void testMiscCoverage() { long seconds = JwtUtils.currentTimeSeconds(); sleep(1000); assertTrue(JwtUtils.currentTimeSeconds() > seconds); + // coverage, also makes sure that the deprecated JsonUtils.NATS_USER_JWT_FORMAT is pointed properly + assertEquals(io.nats.jwt.JwtUtils.NATS_USER_JWT_FORMAT, NATS_USER_JWT_FORMAT); } private static final String BASIC_JSON = "{\"issuer_account\":\"test-issuer-account\",\"type\":\"user\",\"version\":2,\"subs\":-1,\"data\":-1,\"payload\":-1}"; diff --git a/src/test/java/io/nats/service/ServiceTests.java b/src/test/java/io/nats/service/ServiceTests.java index b487817e7..0ff4b267c 100644 --- a/src/test/java/io/nats/service/ServiceTests.java +++ b/src/test/java/io/nats/service/ServiceTests.java @@ -18,10 +18,7 @@ import io.nats.client.impl.JetStreamTestBase; import io.nats.client.impl.MockNatsConnection; import io.nats.client.impl.NatsMessage; -import io.nats.client.support.DateTimeUtils; -import io.nats.client.support.JsonSerializable; -import io.nats.client.support.JsonUtils; -import io.nats.client.support.JsonValue; +import io.nats.client.support.*; import nl.jqno.equalsverifier.EqualsVerifier; import org.junit.jupiter.api.Test; @@ -36,9 +33,9 @@ import java.util.function.Supplier; import static io.nats.client.impl.NatsPackageScopeWorkarounds.getDispatchers; -import static io.nats.client.support.JsonUtils.toKey; import static io.nats.client.support.JsonValueUtils.readInteger; import static io.nats.client.support.JsonValueUtils.readString; +import static io.nats.client.support.JsonWriteUtils.toKey; import static io.nats.client.support.NatsConstants.DOT; import static io.nats.client.support.NatsConstants.EMPTY; import static io.nats.service.Service.SRV_PING; @@ -731,7 +728,7 @@ public void testEndpointConstruction() { .build(); assertEquals(NAME, e.getName()); assertEquals(SUBJECT, e.getSubject()); - assertTrue(JsonUtils.mapEquals(metadata, e.getMetadata())); + assertTrue(Validator.mapEquals(metadata, e.getMetadata())); // some subject testing e = new Endpoint(NAME, "foo.>"); @@ -743,7 +740,7 @@ public void testEndpointConstruction() { e = new Endpoint(NAME, SUBJECT, metadata); assertEquals(NAME, e.getName()); assertEquals(SUBJECT, e.getSubject()); - assertTrue(JsonUtils.mapEquals(metadata, e.getMetadata())); + assertTrue(Validator.mapEquals(metadata, e.getMetadata())); assertThrows(IllegalArgumentException.class, () -> Endpoint.builder().build()); // many names are bad and is required @@ -950,7 +947,7 @@ public void testServiceEndpointConstruction() { .endpointMetadata(metadata) .handler(smh) .build(); - assertTrue(JsonUtils.mapEquals(metadata, se.getMetadata())); + assertTrue(Validator.mapEquals(metadata, se.getMetadata())); IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> ServiceEndpoint.builder().build()); @@ -1155,7 +1152,7 @@ public TestStatsData(JsonValue jv) { @Override public String toJson() { - return JsonUtils.toKey(getClass()) + toJsonValue().toJson(); + return JsonWriteUtils.toKey(getClass()) + toJsonValue().toJson(); } @Override