diff --git a/src/main/java/redis/clients/jedis/Protocol.java b/src/main/java/redis/clients/jedis/Protocol.java index 0b749be..a56bcf0 100644 --- a/src/main/java/redis/clients/jedis/Protocol.java +++ b/src/main/java/redis/clients/jedis/Protocol.java @@ -3,51 +3,38 @@ package redis.clients.jedis; import redis.clients.util.RedisOutputStream; import java.io.*; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; import java.util.ArrayList; import java.util.List; +import static redis.clients.util.RedisOutputStream.CHARSET; + public class Protocol { - public static final Charset CHARSET = Charset.forName("UTF-8"); - private final CharsetEncoder CHARSET_ENCODER = CHARSET.newEncoder(); - - public static final String DOLLAR = "$"; - public static final String ASTERISK = "*"; - public static final String PLUS = "+"; - public static final String MINUS = "-"; - public static final String COLON = ":"; - public static final String COMMAND_DELIMITER = "\r\n"; - public static final byte[] COMMAND_DELIMITER_BYTES = "\r\n".getBytes(CHARSET); public static final int DEFAULT_PORT = 6379; - public static final byte DOLLAR_BYTE = DOLLAR.getBytes(CHARSET)[0]; - public static final byte ASTERISK_BYTE = ASTERISK.getBytes(CHARSET)[0]; - public static final byte PLUS_BYTE = PLUS.getBytes(CHARSET)[0]; - public static final byte MINUS_BYTE = MINUS.getBytes(CHARSET)[0]; - public static final byte COLON_BYTE = COLON.getBytes(CHARSET)[0]; + public static final byte DOLLAR_BYTE = '$'; + public static final byte ASTERISK_BYTE = '*'; + public static final byte PLUS_BYTE = '+'; + public static final byte MINUS_BYTE = '-'; + public static final byte COLON_BYTE = ':'; public void sendCommand(RedisOutputStream os, String name, String... args) { try { os.write(ASTERISK_BYTE); - os.writeInt(args.length + 1); - os.write(COMMAND_DELIMITER_BYTES); + os.writeIntCrLf(args.length + 1); os.write(DOLLAR_BYTE); - os.writeInt(name.length()); - os.write(COMMAND_DELIMITER_BYTES); - os.writeString(name, CHARSET_ENCODER); - os.write(COMMAND_DELIMITER_BYTES); + os.writeIntCrLf(name.length()); + os.writeAsciiCrLf(name); - for (String arg : args) { - final byte[] bytes = arg.getBytes(CHARSET); - int size = bytes.length; - - os.write(DOLLAR_BYTE); - os.writeInt(size); - os.write(COMMAND_DELIMITER_BYTES); - os.write(bytes); - os.write(COMMAND_DELIMITER_BYTES); + for (String str : args) { + os.write(DOLLAR_BYTE); + final int size = RedisOutputStream.utf8Length(str); + os.writeIntCrLf(size); + if(size == str.length()) + os.writeAsciiCrLf(str); + else { + os.writeUtf8CrLf(str); + } } os.flush (); } catch (IOException e) { diff --git a/src/main/java/redis/clients/util/RedisOutputStream.java b/src/main/java/redis/clients/util/RedisOutputStream.java index 85d4324..714ba7b 100644 --- a/src/main/java/redis/clients/util/RedisOutputStream.java +++ b/src/main/java/redis/clients/util/RedisOutputStream.java @@ -1,10 +1,9 @@ package redis.clients.util; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.OutputStream; +import java.io.*; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; @@ -16,7 +15,10 @@ public final class RedisOutputStream extends FilterOutputStream { protected final byte buf[]; protected final ByteBuffer outByteBuffer; + private final CharsetEncoder CHARSET_ENCODER = CHARSET.newEncoder(); + protected int count; + public static final Charset CHARSET = Charset.forName("UTF-8"); public RedisOutputStream(OutputStream out) { this(out, 8192); @@ -41,7 +43,7 @@ public final class RedisOutputStream extends FilterOutputStream { public void write(int b) throws IOException { buf[count++] = (byte) b; - if (count >= buf.length) { + if (count == buf.length) { flushBuffer(); } } @@ -50,8 +52,7 @@ public final class RedisOutputStream extends FilterOutputStream { if (len >= buf.length) { flushBuffer(); out.write(b, off, len); - } - else { + } else { if (len >= buf.length - count) { flushBuffer(); } @@ -61,75 +62,137 @@ public final class RedisOutputStream extends FilterOutputStream { } } - public void writeString(String str, CharsetEncoder encoder) throws IOException { - final CharBuffer in = CharBuffer.wrap(str); - if (in.remaining() == 0) - return; + public void writeAsciiCrLf(String in) throws IOException { + final int size = in.length(); - outByteBuffer.position(count); - - encoder.reset(); - for (;;) { - CoderResult cr; - if (in.hasRemaining()) - cr = encoder.encode(in, outByteBuffer, true); - else - cr = encoder.flush(outByteBuffer); - - count = outByteBuffer.position(); - if(count == buf.length) + for (int i = 0; i != size; ++i) { + buf[count++] = (byte) in.charAt(i); + if (count == buf.length) { flushBuffer(); - - if (cr.isUnderflow()) { - break; } - if (cr.isOverflow()) { - flushBuffer(); - continue; - } - cr.throwException(); } + + writeCrLf(); } - private final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE }; + public static boolean isSurrogate(char ch) { + return ch >= Character.MIN_SURROGATE && ch <= Character.MAX_SURROGATE; + } - private final static byte [] DigitTens = { - '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', - '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', - '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', - '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', - '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', - '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', - '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', - '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', - '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', - '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', - } ; + public static int utf8Length (String str) { + int strLen = str.length(), utfLen = 0; + for(int i = 0; i != strLen; ++i) { + char c = str.charAt(i); + if (c < 0x80) { + utfLen++; + } else if (c < 0x800) { + utfLen += 2; + } else if (isSurrogate(c)) { + i++; + utfLen += 4; + } else { + utfLen += 3; + } + } + return utfLen; + } - private final static byte [] DigitOnes = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - } ; + public void writeCrLf() throws IOException { + if (2 >= buf.length - count) { + flushBuffer(); + } - private final static byte[] digits = { - '0' , '1' , '2' , '3' , '4' , '5' , - '6' , '7' , '8' , '9' , 'a' , 'b' , - 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , - 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , - 'o' , 'p' , 'q' , 'r' , 's' , 't' , - 'u' , 'v' , 'w' , 'x' , 'y' , 'z' + buf[count++] = '\r'; + buf[count++] = '\n'; + } + + public void writeUtf8CrLf(String str) throws IOException { + int strLen = str.length(); + + int i; + for (i = 0; i < strLen; i++) { + char c = str.charAt(i); + if (!(c < 0x80)) break; + buf[count++] = (byte) c; + if(count == buf.length) { + flushBuffer(); + } + } + + for (; i < strLen; i++) { + char c = str.charAt(i); + if (c < 0x80) { + buf[count++] = (byte) c; + if(count == buf.length) { + flushBuffer(); + } + } else if (c < 0x800) { + if(2 < buf.length - count) { + flushBuffer(); + } + buf[count++] = (byte)(0xc0 | (c >> 6)); + buf[count++] = (byte)(0x80 | (c & 0x3f)); + } else if (isSurrogate(c)) { + if(4 < buf.length - count) { + flushBuffer(); + } + int uc = Character.toCodePoint(c, str.charAt(i++)); + buf[count++] = ((byte)(0xf0 | ((uc >> 18)))); + buf[count++] = ((byte)(0x80 | ((uc >> 12) & 0x3f))); + buf[count++] = ((byte)(0x80 | ((uc >> 6) & 0x3f))); + buf[count++] = ((byte)(0x80 | (uc & 0x3f))); + } else { + if(3 < buf.length - count) { + flushBuffer(); + } + buf[count++] =((byte)(0xe0 | ((c >> 12)))); + buf[count++] =((byte)(0x80 | ((c >> 6) & 0x3f))); + buf[count++] =((byte)(0x80 | (c & 0x3f))); + } + } + + writeCrLf(); + } + + private final static int[] sizeTable = {9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE}; + + private final static byte[] DigitTens = { + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', + '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', + '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', + '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', + '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', + '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', + '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', + '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', + '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', }; - public void writeInt(int value) throws IOException { - if(value < 0) { + private final static byte[] DigitOnes = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + }; + + private final static byte[] digits = { + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z' + }; + + public void writeIntCrLf(int value) throws IOException { + if (value < 0) { write('-'); value = -value; } @@ -145,25 +208,25 @@ public final class RedisOutputStream extends FilterOutputStream { int q, r; int charPos = count + size; - char sign = 0; - // Generate two digits per iteration - while ( value >= 65536) { + while (value >= 65536) { q = value / 100; r = value - ((q << 6) + (q << 5) + (q << 2)); value = q; - buf [--charPos] = DigitOnes[r]; - buf [--charPos] = DigitTens[r]; + buf[--charPos] = DigitOnes[r]; + buf[--charPos] = DigitTens[r]; } - for (;;) { - q = (value * 52429) >>> (16+3); - r = value - ((q << 3) + (q << 1)); // r = i-(q*10) ... - buf [--charPos] = digits [r]; + for (; ;) { + q = (value * 52429) >>> (16 + 3); + r = value - ((q << 3) + (q << 1)); + buf[--charPos] = digits[r]; value = q; if (value == 0) break; } count += size; + + writeCrLf(); } public void flush() throws IOException { diff --git a/src/test/java/redis/clients/jedis/tests/JedisTest.java b/src/test/java/redis/clients/jedis/tests/JedisTest.java index 4f28cf7..2af5e42 100644 --- a/src/test/java/redis/clients/jedis/tests/JedisTest.java +++ b/src/test/java/redis/clients/jedis/tests/JedisTest.java @@ -6,9 +6,8 @@ import java.util.Map; import org.junit.Test; import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisException; -import redis.clients.jedis.Protocol; import redis.clients.jedis.tests.commands.JedisCommandTestBase; +import redis.clients.util.RedisOutputStream; public class JedisTest extends JedisCommandTestBase { @Test @@ -24,7 +23,7 @@ public class JedisTest extends JedisCommandTestBase { bigdata[b] = (byte) ((byte) b % 255); } Map hash = new HashMap(); - hash.put("data", new String(bigdata, Protocol.CHARSET)); + hash.put("data", new String(bigdata, RedisOutputStream.CHARSET)); String status = jedis.hmset("foo", hash); assertEquals("OK", status);