All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.lettuce.core.protocol.CommandArgs Maven / Gradle / Ivy

Go to download

Advanced and thread-safe Java Redis client for synchronous, asynchronous, and reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs and much more.

The newest version!
/*
 * Copyright 2011-Present, Redis Ltd. and Contributors
 * All rights reserved.
 *
 * Licensed under the MIT License.
 *
 * This file contains contributions from third-party contributors
 * licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES 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.lettuce.core.protocol;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;

import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.codec.StringCodec;
import io.lettuce.core.codec.ToByteBufEncoder;
import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.internal.LettuceStrings;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.UnpooledByteBufAllocator;

/**
 * Redis command arguments. {@link CommandArgs} is a container for multiple singular arguments. Key and Value arguments are
 * encoded using the {@link RedisCodec} to their byte representation. {@link CommandArgs} provides a fluent style of adding
 * multiple arguments. A {@link CommandArgs} instance can be reused across multiple commands and invocations.
 *
 * 

Example

* *
 * new CommandArgs<>(codec).addKey(key).addValue(value).add(CommandKeyword.FORCE);
 * 
* * @param Key type. * @param Value type. * @author Will Glozer * @author Mark Paluch * @author shikharid */ public class CommandArgs { static final byte[] CRLF = "\r\n".getBytes(StandardCharsets.US_ASCII); protected final RedisCodec codec; final List singularArguments = new ArrayList<>(10); /** * @param codec Codec used to encode/decode keys and values, must not be {@code null}. */ public CommandArgs(RedisCodec codec) { LettuceAssert.notNull(codec, "RedisCodec must not be null"); this.codec = codec; } /** * * @return the number of arguments. */ public int count() { return singularArguments.size(); } /** * Adds a key argument. * * @param key the key * @return the command args. */ public CommandArgs addKey(K key) { singularArguments.add(KeyArgument.of(key, codec)); return this; } /** * Add multiple key arguments. * * @param keys must not be {@code null}. * @return the command args. */ public CommandArgs addKeys(Iterable keys) { LettuceAssert.notNull(keys, "Keys must not be null"); for (K key : keys) { addKey(key); } return this; } /** * Add multiple key arguments. * * @param keys must not be {@code null}. * @return the command args. */ @SafeVarargs public final CommandArgs addKeys(K... keys) { LettuceAssert.notNull(keys, "Keys must not be null"); for (K key : keys) { addKey(key); } return this; } /** * Add a value argument. * * @param value the value * @return the command args. */ public CommandArgs addValue(V value) { singularArguments.add(ValueArgument.of(value, codec)); return this; } /** * Add multiple value arguments. * * @param values must not be {@code null}. * @return the command args. */ public CommandArgs addValues(Iterable values) { LettuceAssert.notNull(values, "Values must not be null"); for (V value : values) { addValue(value); } return this; } /** * Add multiple value arguments. * * @param values must not be {@code null}. * @return the command args. */ @SafeVarargs public final CommandArgs addValues(V... values) { LettuceAssert.notNull(values, "Values must not be null"); for (V value : values) { addValue(value); } return this; } /** * Add a map (hash) argument. * * @param map the map, must not be {@code null}. * @return the command args. */ public CommandArgs add(Map map) { LettuceAssert.notNull(map, "Map must not be null"); for (Map.Entry entry : map.entrySet()) { addKey(entry.getKey()).addValue(entry.getValue()); } return this; } /** * Add a string argument. The argument is represented as bulk string. * * @param s the string. * @return the command args. */ public CommandArgs add(String s) { singularArguments.add(StringArgument.of(s)); return this; } /** * Add a string as char-array. The argument is represented as bulk string. * * @param cs the string. * @return the command args. */ public CommandArgs add(char[] cs) { singularArguments.add(CharArrayArgument.of(cs)); return this; } /** * Add an 64-bit integer (long) argument. * * @param n the argument. * @return the command args. */ public CommandArgs add(long n) { singularArguments.add(IntegerArgument.of(n)); return this; } /** * Add a double argument. * * @param n the double argument. * @return the command args. */ public CommandArgs add(double n) { singularArguments.add(DoubleArgument.of(n)); return this; } /** * Add a byte-array argument. The argument is represented as bulk string. * * @param value the byte-array. * @return the command args. */ public CommandArgs add(byte[] value) { singularArguments.add(BytesArgument.of(value)); return this; } /** * Add a {@link CommandKeyword} argument. The argument is represented as bulk string. * * @param keyword must not be {@code null}. * @return the command args. */ public CommandArgs add(CommandKeyword keyword) { LettuceAssert.notNull(keyword, "CommandKeyword must not be null"); singularArguments.add(ProtocolKeywordArgument.of(keyword)); return this; } /** * Add a {@link CommandType} argument. The argument is represented as bulk string. * * @param type must not be {@code null}. * @return the command args. */ public CommandArgs add(CommandType type) { LettuceAssert.notNull(type, "CommandType must not be null"); singularArguments.add(ProtocolKeywordArgument.of(type)); return this; } /** * Add a {@link ProtocolKeyword} argument. The argument is represented as bulk string. * * @param keyword the keyword, must not be {@code null} * @return the command args. */ public CommandArgs add(ProtocolKeyword keyword) { LettuceAssert.notNull(keyword, "CommandKeyword must not be null"); singularArguments.add(ProtocolKeywordArgument.of(keyword)); return this; } /** * Add all arguments from {@link CommandArgs} * * @param args the args, must not be {@code null} * @return the command args. * @since 6.2 */ public CommandArgs addAll(CommandArgs args) { LettuceAssert.notNull(args, "CommandArgs must not be null"); this.singularArguments.addAll(args.singularArguments); return this; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); ByteBuf buffer = UnpooledByteBufAllocator.DEFAULT.buffer(singularArguments.size() * 10); encode(buffer); buffer.resetReaderIndex(); byte[] bytes = new byte[buffer.readableBytes()]; buffer.readBytes(bytes); sb.append(" [buffer=").append(new String(bytes)); sb.append(']'); buffer.release(); return sb.toString(); } /** * Returns a command string representation of {@link CommandArgs} with annotated key and value parameters. * * {@code args.addKey("mykey").add(2.0)} will return {@code key 2.0}. * * @return the command string representation. */ public String toCommandString() { return LettuceStrings.collectionToDelimitedString(singularArguments, " ", "", ""); } /** * Returns the first integer argument. * * @return the first integer argument or {@code null}. */ @Deprecated public Long getFirstInteger() { return CommandArgsAccessor.getFirstInteger(this); } /** * Returns the first string argument. * * @return the first string argument or {@code null}. */ @Deprecated public String getFirstString() { return CommandArgsAccessor.getFirstString(this); } /** * Returns the first key argument in its byte-encoded representation. * * @return the first key argument in its byte-encoded representation or {@code null}. */ public ByteBuffer getFirstEncodedKey() { return CommandArgsAccessor.encodeFirstKey(this); } /** * Encode the {@link CommandArgs} and write the arguments to the {@link ByteBuf}. * * @param buf the target buffer. */ public void encode(ByteBuf buf) { buf.touch("CommandArgs.encode(…)"); for (SingularArgument singularArgument : singularArguments) { singularArgument.encode(buf); } } /** * Single argument wrapper that can be encoded. */ static abstract class SingularArgument { /** * Encode the argument and write it to the {@code buffer}. * * @param buffer */ abstract void encode(ByteBuf buffer); } static class BytesArgument extends SingularArgument { final byte[] val; private BytesArgument(byte[] val) { this.val = val; } static BytesArgument of(byte[] val) { return new BytesArgument(val); } @Override void encode(ByteBuf buffer) { writeBytes(buffer, val); } static void writeBytes(ByteBuf buffer, byte[] value) { buffer.writeByte('$'); IntegerArgument.writeInteger(buffer, value.length); buffer.writeBytes(CRLF); buffer.writeBytes(value); buffer.writeBytes(CRLF); } @Override public String toString() { return Base64.getEncoder().encodeToString(val); } } static class ProtocolKeywordArgument extends BytesArgument { private final ProtocolKeyword protocolKeyword; private ProtocolKeywordArgument(ProtocolKeyword protocolKeyword) { super(protocolKeyword.getBytes()); this.protocolKeyword = protocolKeyword; } static BytesArgument of(ProtocolKeyword protocolKeyword) { if (protocolKeyword instanceof CommandType) { return CommandTypeCache.cache[((Enum) protocolKeyword).ordinal()]; } if (protocolKeyword instanceof CommandKeyword) { return CommandKeywordCache.cache[((Enum) protocolKeyword).ordinal()]; } return ProtocolKeywordArgument.of(protocolKeyword.getBytes()); } @Override public String toString() { return protocolKeyword.toString(); } } static class CommandTypeCache { static final ProtocolKeywordArgument cache[]; static { CommandType[] values = CommandType.values(); cache = new ProtocolKeywordArgument[values.length]; for (int i = 0; i < cache.length; i++) { cache[i] = new ProtocolKeywordArgument(values[i]); } } } static class CommandKeywordCache { static final ProtocolKeywordArgument cache[]; static { CommandKeyword[] values = CommandKeyword.values(); cache = new ProtocolKeywordArgument[values.length]; for (int i = 0; i < cache.length; i++) { cache[i] = new ProtocolKeywordArgument(values[i]); } } } static class ByteBufferArgument { static void writeByteBuffer(ByteBuf target, ByteBuffer value) { target.writeByte('$'); IntegerArgument.writeInteger(target, value.remaining()); target.writeBytes(CRLF); target.writeBytes(value); target.writeBytes(CRLF); } static void writeByteBuf(ByteBuf target, ByteBuf value) { target.writeByte('$'); IntegerArgument.writeInteger(target, value.readableBytes()); target.writeBytes(CRLF); target.writeBytes(value); target.writeBytes(CRLF); } } static class IntegerArgument extends SingularArgument { final long val; private IntegerArgument(long val) { this.val = val; } static IntegerArgument of(long val) { if (val >= 0 && val < IntegerCache.cache.length) { return IntegerCache.cache[(int) val]; } if (val < 0 && val > Integer.MIN_VALUE && -val < IntegerCache.cache.length) { return IntegerCache.negativeCache[(int) -val]; } return new IntegerArgument(val); } @Override void encode(ByteBuf target) { StringArgument.writeString(target, Long.toString(val)); } @Override public String toString() { return "" + val; } static void writeInteger(ByteBuf target, long value) { if (value < 10) { target.writeByte((byte) ('0' + value)); return; } String asString = Long.toString(value); for (int i = 0; i < asString.length(); i++) { target.writeByte((byte) asString.charAt(i)); } } } static class IntegerCache { static final IntegerArgument[] cache; static final IntegerArgument[] negativeCache; static { int high = Integer.getInteger("io.lettuce.core.CommandArgs.IntegerCache", 128); cache = new IntegerArgument[high]; negativeCache = new IntegerArgument[high]; for (int i = 0; i < high; i++) { cache[i] = new IntegerArgument(i); negativeCache[i] = new IntegerArgument(-i); } } } static class DoubleArgument extends SingularArgument { final double val; private DoubleArgument(double val) { this.val = val; } static DoubleArgument of(double val) { return new DoubleArgument(val); } @Override void encode(ByteBuf target) { StringArgument.writeString(target, Double.toString(val)); } @Override public String toString() { return "" + val; } } static class StringArgument extends SingularArgument { final String val; private StringArgument(String val) { this.val = val; } static StringArgument of(String val) { return new StringArgument(val); } @Override void encode(ByteBuf target) { writeString(target, val); } static void writeString(ByteBuf target, String value) { target.writeByte('$'); IntegerArgument.writeInteger(target, value.length()); target.writeBytes(CRLF); for (int i = 0; i < value.length(); i++) { target.writeByte((byte) value.charAt(i)); } target.writeBytes(CRLF); } @Override public String toString() { return val; } } static class CharArrayArgument extends SingularArgument { final char[] val; private CharArrayArgument(char[] val) { this.val = val; } static CharArrayArgument of(char[] val) { return new CharArrayArgument(val); } @Override void encode(ByteBuf target) { writeString(target, val); } static void writeString(ByteBuf target, char[] value) { target.writeByte('$'); IntegerArgument.writeInteger(target, value.length); target.writeBytes(CRLF); for (char c : value) { target.writeByte((byte) c); } target.writeBytes(CRLF); } @Override public String toString() { return new String(val); } } static class KeyArgument extends SingularArgument { final K key; final RedisCodec codec; private KeyArgument(K key, RedisCodec codec) { this.key = key; this.codec = codec; } static KeyArgument of(K key, RedisCodec codec) { return new KeyArgument<>(key, codec); } @SuppressWarnings("unchecked") @Override void encode(ByteBuf target) { if (codec instanceof ToByteBufEncoder) { CommandArgs.encode(target, (ToByteBufEncoder) codec, key, ToByteBufEncoder::encodeKey); return; } ByteBufferArgument.writeByteBuffer(target, codec.encodeKey(key)); } @Override public String toString() { return String.format("key<%s>", new StringCodec().decodeKey(codec.encodeKey(key))); } } static class ValueArgument extends SingularArgument { final V val; final RedisCodec codec; private ValueArgument(V val, RedisCodec codec) { this.val = val; this.codec = codec; } static ValueArgument of(V val, RedisCodec codec) { return new ValueArgument<>(val, codec); } @SuppressWarnings("unchecked") @Override void encode(ByteBuf target) { if (codec instanceof ToByteBufEncoder) { CommandArgs.encode(target, (ToByteBufEncoder) codec, val, ToByteBufEncoder::encodeValue); return; } ByteBufferArgument.writeByteBuffer(target, codec.encodeValue(val)); } @Override public String toString() { return String.format("value<%s>", new StringCodec().decodeValue(codec.encodeValue(val))); } } static void encode(ByteBuf target, ToByteBufEncoder encoder, T item, EncodeFunction encodeFunction) { if (encoder.isEstimateExact()) { target.writeByte('$'); IntegerArgument.writeInteger(target, encoder.estimateSize(item)); target.writeBytes(CRLF); encodeFunction.encode(encoder, item, target); target.writeBytes(CRLF); } else { ByteBuf temporaryBuffer = target.alloc().buffer(encoder.estimateSize(item) + 6); try { encodeFunction.encode(encoder, item, temporaryBuffer); ByteBufferArgument.writeByteBuf(target, temporaryBuffer); } finally { temporaryBuffer.release(); } } } interface EncodeFunction { void encode(ToByteBufEncoder encoder, T item, ByteBuf target); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy