16
16
17
17
package redis .clients .util ;
18
18
19
- import java .io .FilterInputStream ;
20
- import java .io .IOException ;
21
- import java .io .InputStream ;
19
+ import java .io .*;
22
20
23
21
import redis .clients .jedis .exceptions .JedisConnectionException ;
24
22
23
+ /**
24
+ * This class assumes (to some degree) that we are reading a RESP stream. As such it assumes
25
+ * certain conventions regarding CRLF line termination. It also assumes that if the Protocol
26
+ * layer requires a byte that if that byte is not there it is a stream error.
27
+ */
25
28
public class RedisInputStream extends FilterInputStream {
26
29
27
30
protected final byte buf [];
@@ -40,73 +43,172 @@ public RedisInputStream(InputStream in) {
40
43
this (in , 8192 );
41
44
}
42
45
43
- public byte readByte () throws IOException {
44
- if (count == limit ) {
45
- fill ();
46
- }
47
-
46
+ public byte readByte () throws JedisConnectionException {
47
+ ensureFill ();
48
48
return buf [count ++];
49
49
}
50
50
51
51
public String readLine () {
52
- int b ;
53
- byte c ;
54
- StringBuilder sb = new StringBuilder ();
55
-
56
- try {
57
- while (true ) {
58
- if (count == limit ) {
59
- fill ();
60
- }
61
- if (limit == -1 )
62
- break ;
52
+ final StringBuilder sb = new StringBuilder ();
53
+ while (true ) {
54
+ ensureFill ();
55
+
56
+ byte b = buf [count ++];
57
+ if (b == '\r' ) {
58
+ ensureFill (); // Must be one more byte
63
59
64
- b = buf [count ++];
65
- if (b == '\r' ) {
66
- if (count == limit ) {
67
- fill ();
68
- }
69
-
70
- if (limit == -1 ) {
71
- sb .append ((char ) b );
72
- break ;
73
- }
74
-
75
- c = buf [count ++];
76
- if (c == '\n' ) {
77
- break ;
78
- }
79
- sb .append ((char ) b );
80
- sb .append ((char ) c );
81
- } else {
82
- sb .append ((char ) b );
60
+ byte c = buf [count ++];
61
+ if (c == '\n' ) {
62
+ break ;
83
63
}
64
+ sb .append ((char ) b );
65
+ sb .append ((char ) c );
66
+ } else {
67
+ sb .append ((char ) b );
84
68
}
85
- } catch (IOException e ) {
86
- throw new JedisConnectionException (e );
87
69
}
88
- String reply = sb .toString ();
70
+
71
+ final String reply = sb .toString ();
89
72
if (reply .length () == 0 ) {
90
- throw new JedisConnectionException (
91
- "It seems like server has closed the connection." );
73
+ throw new JedisConnectionException ("It seems like server has closed the connection." );
92
74
}
75
+
93
76
return reply ;
94
77
}
95
78
96
- public int read (byte [] b , int off , int len ) throws IOException {
97
- if (count == limit ) {
98
- fill ();
99
- if (limit == -1 )
100
- return -1 ;
79
+ public byte [] readLineBytes () {
80
+
81
+ /* This operation should only require one fill. In that typical
82
+ case we optimize allocation and copy of the byte array. In the
83
+ edge case where more than one fill is required then we take a
84
+ slower path and expand a byte array output stream as is
85
+ necessary. */
86
+
87
+ ensureFill ();
88
+
89
+ int pos = count ;
90
+ final byte [] buf = this .buf ;
91
+ while (true ) {
92
+ if (pos == limit ) {
93
+ return readLineBytesSlowly ();
94
+ }
95
+
96
+ if (buf [pos ++] == '\r' ) {
97
+ if (pos == limit ) {
98
+ return readLineBytesSlowly ();
99
+ }
100
+
101
+ if (buf [pos ++] == '\n' ) {
102
+ break ;
103
+ }
104
+ }
105
+ }
106
+
107
+ final int N = (pos - count ) - 2 ;
108
+ final byte [] line = new byte [N ];
109
+ System .arraycopy (buf , count , line , 0 , N );
110
+ count = pos ;
111
+ return line ;
112
+ }
113
+
114
+ /**
115
+ * Slow path in case a line of bytes cannot be read in one #fill() operation. This is still faster
116
+ * than creating the StrinbBuilder, String, then encoding as byte[] in Protocol, then decoding back
117
+ * into a String.
118
+ */
119
+ private byte [] readLineBytesSlowly () {
120
+ ByteArrayOutputStream bout = null ;
121
+ while (true ) {
122
+ ensureFill ();
123
+
124
+ byte b = buf [count ++];
125
+ if (b == '\r' ) {
126
+ ensureFill (); // Must be one more byte
127
+
128
+ byte c = buf [count ++];
129
+ if (c == '\n' ) {
130
+ break ;
131
+ }
132
+
133
+ if (bout == null ) {
134
+ bout = new ByteArrayOutputStream (16 );
135
+ }
136
+
137
+ bout .write (b );
138
+ bout .write (c );
139
+ } else {
140
+ if (bout == null ) {
141
+ bout = new ByteArrayOutputStream (16 );
142
+ }
143
+
144
+ bout .write (b );
145
+ }
146
+ }
147
+
148
+ return bout == null ? new byte [0 ] : bout .toByteArray ();
149
+ }
150
+
151
+ public int readIntCrLf () {
152
+ return (int )readLongCrLf ();
153
+ }
154
+
155
+ public long readLongCrLf () {
156
+ final byte [] buf = this .buf ;
157
+
158
+ ensureFill ();
159
+
160
+ final boolean isNeg = buf [count ] == '-' ;
161
+ if (isNeg ) {
162
+ ++count ;
101
163
}
164
+
165
+ long value = 0 ;
166
+ while (true ) {
167
+ ensureFill ();
168
+
169
+ final int b = buf [count ++];
170
+ if (b == '\r' ) {
171
+ ensureFill ();
172
+
173
+ if (buf [count ++] != '\n' ) {
174
+ throw new JedisConnectionException ("Unexpected character!" );
175
+ }
176
+
177
+ break ;
178
+ }
179
+ else {
180
+ value = value * 10 + b - '0' ;
181
+ }
182
+ }
183
+
184
+ return (isNeg ? -value : value );
185
+ }
186
+
187
+ public int read (byte [] b , int off , int len ) throws JedisConnectionException {
188
+ ensureFill ();
189
+
102
190
final int length = Math .min (limit - count , len );
103
191
System .arraycopy (buf , count , b , off , length );
104
192
count += length ;
105
193
return length ;
106
194
}
107
195
108
- private void fill () throws IOException {
109
- limit = in .read (buf );
110
- count = 0 ;
196
+ /**
197
+ * This methods assumes there are required bytes to be read. If we cannot read
198
+ * anymore bytes an exception is thrown to quickly ascertain that the stream
199
+ * was smaller than expected.
200
+ */
201
+ private void ensureFill () throws JedisConnectionException {
202
+ if (count >= limit ) {
203
+ try {
204
+ limit = in .read (buf );
205
+ count = 0 ;
206
+ if (limit == -1 ) {
207
+ throw new JedisConnectionException ("Unexpected end of stream." );
208
+ }
209
+ } catch (IOException e ) {
210
+ throw new JedisConnectionException (e );
211
+ }
212
+ }
111
213
}
112
214
}
0 commit comments