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

com.lambdaworks.redis.protocol.CommandArgs Maven / Gradle / Ivy

/*
 * Copyright 2011-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambdaworks.redis.protocol;

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

import com.lambdaworks.redis.LettuceStrings;
import com.lambdaworks.redis.codec.ByteArrayCodec;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.codec.StringCodec;
import com.lambdaworks.redis.codec.ToByteBufEncoder;
import com.lambdaworks.redis.internal.LettuceAssert;

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

* Usage *

* *
 *     
 *         new CommandArgs<>(codec).addKey(key).addValue(value).add(CommandKeyword.FORCE);
 *     
 * 
* * @param Key type. * @param Value type. * @author Will Glozer * @author Mark Paluch */ public class CommandArgs { static final byte[] CRLF = "\r\n".getBytes(LettuceCharsets.ASCII); protected final RedisCodec codec; private final List singularArguments = new ArrayList<>(10); private Long firstInteger; private String firstString; private ByteBuffer firstEncodedKey; private K firstKey; /** * * @param codec Codec used to encode/decode keys and values, must not be {@literal 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) { if (firstKey == null) { firstKey = key; } singularArguments.add(KeyArgument.of(key, codec)); return this; } /** * Add multiple key arguments. * * @param keys must not be {@literal 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 {@literal null}. * @return the command args. */ public 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 {@literal 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 {@literal null}. * @return the command args. */ public 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 {@literal 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) { if (firstString == null) { firstString = s; } singularArguments.add(StringArgument.of(s)); return this; } /** * Add an 64-bit integer (long) argument. * * @param n the argument. * @return the command args. */ public CommandArgs add(long n) { if (firstInteger == null) { firstInteger = 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 {@literal null}. * @return the command args. */ public CommandArgs add(CommandKeyword keyword) { LettuceAssert.notNull(keyword, "CommandKeyword must not be null"); return add((ProtocolKeyword) keyword); } /** * Add a {@link CommandType} argument. The argument is represented as bulk string. * * @param type must not be {@literal null}. * @return the command args. */ public CommandArgs add(CommandType type) { LettuceAssert.notNull(type, "CommandType must not be null"); return add(type.bytes); } /** * Add a {@link ProtocolKeyword} argument. The argument is represented as bulk string. * * @param keyword the keyword, must not be {@literal null} * @return the command args. */ public CommandArgs add(ProtocolKeyword keyword) { LettuceAssert.notNull(keyword, "CommandKeyword must not be null"); return add(keyword.getBytes()); } @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 {@literal null}. */ public Long getFirstInteger() { return firstInteger; } /** * Returns the first string argument. * * @return the first string argument or {@literal null}. */ public String getFirstString() { return firstString; } /** * Returns the first key argument in its byte-encoded representation. * * @return the first key argument in its byte-encoded representation or {@literal null}. */ public ByteBuffer getFirstEncodedKey() { if (firstKey == null) { return null; } if (firstEncodedKey == null) { firstEncodedKey = codec.encodeKey(firstKey); } return firstEncodedKey.duplicate(); } /** * Encode the {@link CommandArgs} and write the arguments to the {@link ByteBuf}. * * @param buf the target buffer. */ public void encode(ByteBuf buf) { 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 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]; } 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 { int high = Integer.getInteger("biz.paluch.redis.CommandArgs.IntegerCache", 128); cache = new IntegerArgument[high]; for (int i = 0; i < high; i++) { cache[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 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); } @Override void encode(ByteBuf target) { if (codec == ExperimentalByteArrayCodec.INSTANCE) { ((ExperimentalByteArrayCodec) codec).encodeKey(target, (byte[]) key); return; } if (codec instanceof ToByteBufEncoder) { ToByteBufEncoder toByteBufEncoder = (ToByteBufEncoder) codec; ByteBuf temporaryBuffer = target.alloc().buffer(toByteBufEncoder.estimateSize(key)); toByteBufEncoder.encodeKey(key, temporaryBuffer); ByteBufferArgument.writeByteBuf(target, temporaryBuffer); temporaryBuffer.release(); 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); } @Override void encode(ByteBuf target) { if (codec == ExperimentalByteArrayCodec.INSTANCE) { ((ExperimentalByteArrayCodec) codec).encodeValue(target, (byte[]) val); return; } if (codec instanceof ToByteBufEncoder) { ToByteBufEncoder toByteBufEncoder = (ToByteBufEncoder) codec; ByteBuf temporaryBuffer = target.alloc().buffer(toByteBufEncoder.estimateSize(val)); toByteBufEncoder.encodeValue(val, temporaryBuffer); ByteBufferArgument.writeByteBuf(target, temporaryBuffer); temporaryBuffer.release(); return; } ByteBufferArgument.writeByteBuffer(target, codec.encodeValue(val)); } @Override public String toString() { return String.format("value<%s>", new StringCodec().decodeValue(codec.encodeValue(val))); } } /** * This codec writes directly {@code byte[]} to the target buffer without wrapping it in a {@link ByteBuffer} to reduce GC * pressure. */ public static final class ExperimentalByteArrayCodec extends ByteArrayCodec { public static final ExperimentalByteArrayCodec INSTANCE = new ExperimentalByteArrayCodec(); private ExperimentalByteArrayCodec() { } public void encodeKey(ByteBuf target, byte[] key) { target.writeByte('$'); if (key == null) { target.writeBytes("0\r\n\r\n".getBytes()); return; } IntegerArgument.writeInteger(target, key.length); target.writeBytes(CRLF); target.writeBytes(key); target.writeBytes(CRLF); } public void encodeValue(ByteBuf target, byte[] value) { encodeKey(target, value); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy