blob: 79a609fad0c8f7a24b1e0c1cfb64f7e020eb7297 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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 org.apache.geode.redis.internal.netty;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.apache.geode.annotations.internal.MakeImmutable;
import org.apache.geode.redis.internal.data.ByteArrayWrapper;
/**
* This is a safe encoder and decoder for all redis matching needs
*/
public class Coder {
/*
* Take no chances on char to byte conversions with default charsets on jvms, so we'll hard code
* the UTF-8 symbol values as bytes here
*/
/**
* byte identifier of a bulk string
*/
public static final byte BULK_STRING_ID = 36; // '$'
/**
* byte identifier of an array
*/
public static final byte ARRAY_ID = 42; // '*'
/**
* byte identifier of an error
*/
public static final byte ERROR_ID = 45; // '-'
/**
* byte identifier of an integer
*/
public static final byte INTEGER_ID = 58; // ':'
public static final byte NUMBER_1_BYTE = 0x31; // '1'
/**
* byte identifier of a simple string
*/
public static final byte SIMPLE_STRING_ID = 43; // '+'
public static final String CRLF = "\r\n";
@MakeImmutable
public static final byte[] CRLFar = stringToBytes(CRLF); // {13, 10} == {'\r', '\n'}
/**
* byte array of a nil response
*/
@MakeImmutable
public static final byte[] bNIL = stringToBytes("$-1\r\n"); // {'$', '-', '1', '\r', '\n'};
/**
* byte array of an empty array
*/
@MakeImmutable
public static final byte[] bEMPTY_ARRAY = stringToBytes("*0\r\n"); // {'*', '0', '\r', '\n'};
/**
* byte array of an empty string
*/
@MakeImmutable
public static final byte[] bEMPTY_STRING = stringToBytes("$0\r\n\r\n");
@MakeImmutable
public static final byte[] err = stringToBytes("ERR ");
@MakeImmutable
public static final byte[] wrongType = stringToBytes("WRONGTYPE ");
/**
* The charset being used by this coder, {@value #CHARSET}.
*/
public static final String CHARSET = "UTF-8";
@MakeImmutable
protected static final DecimalFormat decimalFormatter = new DecimalFormat("#");
static {
decimalFormatter.setMaximumFractionDigits(10);
}
/**
* Positive infinity string
*/
public static final String P_INF = "+inf";
/**
* Negative infinity string
*/
public static final String N_INF = "-inf";
public static ByteBuf getBulkStringResponse(ByteBufAllocator alloc, Object v)
throws CoderException {
ByteBuf response;
byte[] toWrite;
if (v == null) {
response = alloc.buffer();
response.writeBytes(bNIL);
} else if (v instanceof byte[]) {
byte[] value = (byte[]) v;
response = alloc.buffer(value.length + 20);
toWrite = value;
writeStringResponse(response, toWrite);
} else if (v instanceof ByteArrayWrapper) {
byte[] value = ((ByteArrayWrapper) v).toBytes();
response = alloc.buffer(value.length + 20);
toWrite = value;
writeStringResponse(response, toWrite);
} else if (v instanceof Double) {
response = alloc.buffer();
toWrite = doubleToBytes(((Double) v).doubleValue());
writeStringResponse(response, toWrite);
} else if (v instanceof String) {
String value = (String) v;
response = alloc.buffer(value.length() + 20);
toWrite = stringToBytes(value);
writeStringResponse(response, toWrite);
} else if (v instanceof Integer) {
response = alloc.buffer(15);
response.writeByte(INTEGER_ID);
response.writeBytes(intToBytes((Integer) v));
response.writeBytes(CRLFar);
} else if (v instanceof Long) {
response = alloc.buffer(15);
response.writeByte(INTEGER_ID);
response.writeBytes(intToBytes(((Long) v).intValue()));
response.writeBytes(CRLFar);
} else {
throw new CoderException();
}
return response;
}
private static void writeStringResponse(ByteBuf response, byte[] toWrite) {
response.writeByte(BULK_STRING_ID);
response.writeBytes(intToBytes(toWrite.length));
response.writeBytes(CRLFar);
response.writeBytes(toWrite);
response.writeBytes(CRLFar);
}
public static ByteBuf getFlattenedArrayResponse(ByteBufAllocator alloc,
Collection<Collection<?>> items)
throws CoderException {
ByteBuf response = alloc.buffer();
for (Object next : items) {
writeCollectionOrString(alloc, response, next);
}
return response;
}
public static ByteBuf getArrayResponse(ByteBufAllocator alloc, Collection<?> items)
throws CoderException {
ByteBuf response = alloc.buffer();
response.writeByte(ARRAY_ID);
response.writeBytes(intToBytes(items.size()));
response.writeBytes(CRLFar);
for (Object next : items) {
writeCollectionOrString(alloc, response, next);
}
return response;
}
private static void writeCollectionOrString(ByteBufAllocator alloc, ByteBuf response, Object next)
throws CoderException {
ByteBuf tmp = null;
try {
if (next instanceof Collection) {
Collection<?> nextItems = (Collection<?>) next;
tmp = getArrayResponse(alloc, nextItems);
response.writeBytes(tmp);
} else {
tmp = getBulkStringResponse(alloc, next);
response.writeBytes(tmp);
}
} finally {
if (tmp != null) {
tmp.release();
}
}
}
public static ByteBuf getScanResponse(ByteBufAllocator alloc, BigInteger cursor,
List<Object> scanResult) {
ByteBuf response = alloc.buffer();
response.writeByte(ARRAY_ID);
response.writeBytes(intToBytes(2));
response.writeBytes(CRLFar);
response.writeByte(BULK_STRING_ID);
byte[] cursorBytes = stringToBytes(cursor.toString());
response.writeBytes(intToBytes(cursorBytes.length));
response.writeBytes(CRLFar);
response.writeBytes(cursorBytes);
response.writeBytes(CRLFar);
response.writeByte(ARRAY_ID);
response.writeBytes(intToBytes(scanResult.size()));
response.writeBytes(CRLFar);
for (Object nextObject : scanResult) {
if (nextObject instanceof String) {
String next = (String) nextObject;
response.writeByte(BULK_STRING_ID);
response.writeBytes(intToBytes(next.length()));
response.writeBytes(CRLFar);
response.writeBytes(stringToBytes(next));
response.writeBytes(CRLFar);
} else if (nextObject instanceof ByteArrayWrapper) {
byte[] next = ((ByteArrayWrapper) nextObject).toBytes();
response.writeByte(BULK_STRING_ID);
response.writeBytes(intToBytes(next.length));
response.writeBytes(CRLFar);
response.writeBytes(next);
response.writeBytes(CRLFar);
}
}
return response;
}
public static ByteBuf getEmptyArrayResponse(ByteBufAllocator alloc) {
ByteBuf buf = alloc.buffer().writeBytes(bEMPTY_ARRAY);
return buf;
}
public static ByteBuf getEmptyStringResponse(ByteBufAllocator alloc) {
ByteBuf buf = alloc.buffer().writeBytes(bEMPTY_STRING);
return buf;
}
public static ByteBuf getSimpleStringResponse(ByteBufAllocator alloc, String string) {
byte[] simpAr = stringToBytes(string);
return getSimpleStringResponse(alloc, simpAr);
}
public static ByteBuf getSimpleStringResponse(ByteBufAllocator alloc, byte[] byteArray) {
ByteBuf response = alloc.buffer(byteArray.length + 20);
response.writeByte(SIMPLE_STRING_ID);
response.writeBytes(byteArray);
response.writeBytes(CRLFar);
return response;
}
public static ByteBuf getErrorResponse(ByteBufAllocator alloc, String error) {
byte[] errorAr = stringToBytes(error);
ByteBuf response = alloc.buffer(errorAr.length + 25);
response.writeByte(ERROR_ID);
response.writeBytes(err);
response.writeBytes(errorAr);
response.writeBytes(CRLFar);
return response;
}
public static ByteBuf getCustomErrorResponse(ByteBufAllocator alloc, String error) {
byte[] errorAr = stringToBytes(error);
ByteBuf response = alloc.buffer(errorAr.length + 25);
response.writeByte(ERROR_ID);
response.writeBytes(errorAr);
response.writeBytes(CRLFar);
return response;
}
public static ByteBuf getWrongTypeResponse(ByteBufAllocator alloc, String error) {
byte[] errorAr = stringToBytes(error);
ByteBuf response = alloc.buffer(errorAr.length + 31);
response.writeByte(ERROR_ID);
response.writeBytes(wrongType);
response.writeBytes(errorAr);
response.writeBytes(CRLFar);
return response;
}
public static ByteBuf getIntegerResponse(ByteBufAllocator alloc, int integer) {
ByteBuf response = alloc.buffer(15);
response.writeByte(INTEGER_ID);
response.writeBytes(intToBytes(integer));
response.writeBytes(CRLFar);
return response;
}
public static ByteBuf getIntegerResponse(ByteBufAllocator alloc, long l) {
ByteBuf response = alloc.buffer(25);
response.writeByte(INTEGER_ID);
response.writeBytes(longToBytes(l));
response.writeBytes(CRLFar);
return response;
}
public static ByteBuf getDoubleResponse(ByteBufAllocator alloc, double d) {
ByteBuf response = alloc.buffer();
writeStringResponse(response, doubleToBytes(d));
return response;
}
public static ByteBuf getNilResponse(ByteBufAllocator alloc) {
ByteBuf buf = alloc.buffer().writeBytes(bNIL);
return buf;
}
public static String bytesToString(byte[] bytes) {
if (bytes == null) {
return null;
}
return new String(bytes);
}
public static String doubleToString(double d) {
if (d == Double.POSITIVE_INFINITY) {
return "Infinity";
}
if (d == Double.NEGATIVE_INFINITY) {
return "-Infinity";
}
String stringValue = String.valueOf(d);
if (stringValue.endsWith(".0")) {
return (stringValue.substring(0, stringValue.length() - 2));
}
return stringValue;
}
public static byte[] stringToBytes(String string) {
if (string == null) {
return null;
}
return string.getBytes();
}
/*
* These toByte methods convert to byte arrays of the string representation of the input, not
* literal to byte
*/
public static byte[] intToBytes(int i) {
return stringToBytes(String.valueOf(i));
}
public static byte[] longToBytes(long l) {
return stringToBytes(String.valueOf(l));
}
public static byte[] doubleToBytes(double d) {
return stringToBytes(doubleToString(d));
}
public static int bytesToInt(byte[] bytes) {
return Integer.parseInt(bytesToString(bytes));
}
public static long bytesToLong(byte[] bytes) {
return Long.parseLong(bytesToString(bytes));
}
/**
* A conversion where the byte array actually represents a string, so it is converted as a string
* not as a literal double
*
* @param bytes Array holding double
* @return Parsed value
* @throws NumberFormatException if bytes to string does not yield a convertible double
*/
public static double bytesToDouble(byte[] bytes) {
return stringToDouble(bytesToString(bytes));
}
/**
* Redis specific manner to parse floats
*
* @param d String holding double
* @return Value of string
* @throws NumberFormatException if the double cannot be parsed
*/
public static double stringToDouble(String d) {
if (d.equalsIgnoreCase(P_INF)) {
return Double.POSITIVE_INFINITY;
} else if (d.equalsIgnoreCase(N_INF)) {
return Double.NEGATIVE_INFINITY;
} else {
return Double.parseDouble(d);
}
}
}