1 /*
2  * Hunt - A redis client library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.redis.util.RedisOutputStream;
13 
14 import hunt.Exceptions;
15 import hunt.logging.ConsoleLogger;
16 import hunt.stream.Common;
17 import hunt.stream.FilterInputStream;
18 import hunt.stream.FilterOutputStream;
19 
20 
21 /**
22  * The class implements a buffered output stream without synchronization There are also special
23  * operations like in-place string encoding. This stream fully ignore mark/reset and should not be
24  * used outside Redis
25  */
26 class RedisOutputStream : FilterOutputStream {
27     protected byte[] buf;
28 
29     protected int count;
30 
31 // dfmt off
32     private enum int[] sizeTable = [
33             9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, int.max
34         ];
35 
36     private enum byte[] DigitTens = [
37             '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1',
38             '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2',
39             '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3',
40             '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5',
41             '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6',
42             '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7',
43             '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9',
44             '9', '9', '9', '9', '9', '9', '9', '9', '9'
45         ];
46 
47     private enum byte[] DigitOnes = [
48             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2',
49             '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5',
50             '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8',
51             '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1',
52             '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4',
53             '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7',
54             '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0',
55             '1', '2', '3', '4', '5', '6', '7', '8', '9'
56         ];
57 
58     private enum byte[] digits = [
59             '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
60             'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
61             'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
62         ];
63 // dfmt on
64 
65     this(OutputStream outputStream) {
66         this(outputStream, 8192);
67     }
68 
69     this(OutputStream outputStream, int size) {
70         super(outputStream);
71         if (size <= 0) {
72             throw new IllegalArgumentException("Buffer size <= 0");
73         }
74         buf = new byte[size];
75     }
76 
77     private void flushBuffer() {
78         if (count > 0) {
79             version(HUNT_REDIS_DEBUG_MORE) {
80                 if(count<32) {
81                     tracef("outgoing: %s", cast(string)buf[0 .. count]);
82                 } else {
83                     tracef("outgoing: %s", cast(string)buf[0 .. 32]);
84                 }
85             }
86             outputStream.write(buf, 0, count);
87             count = 0;
88         }
89     }
90 
91     void write(byte b) {
92         if (count == buf.length) {
93             flushBuffer();
94         }
95         buf[count++] = b;
96     }
97 
98     alias write = FilterOutputStream.write;
99     alias write = OutputStream.write;
100 
101     override void write(byte[] b) {
102         write(b, 0, cast(int) b.length);
103     }
104 
105     override void write(byte[] b, int off, int len) {
106         version(HUNT_REDIS_DEBUG_MORE) { 
107             infof("%d bytes: %(%02X %)", len, b[off ..  off+len]);
108         }
109 
110         if (len >= buf.length) {
111             flushBuffer();
112             outputStream.write(b, off, len);
113         } else {
114             if (len >= buf.length - count) {
115                 flushBuffer();
116             }
117 
118             // System.arraycopy(b, off, buf, count, len);
119             buf[count .. count + len] = b[off .. off + len];
120             count += len;
121         }
122     }
123 
124     void writeCrLf() {
125         if (2 >= buf.length - count) {
126             flushBuffer();
127         }
128 
129         buf[count++] = '\r';
130         buf[count++] = '\n';
131     }
132 
133     void writeIntCrLf(int value) {
134         if (value < 0) {
135             write(cast(byte) '-');
136             value = -value;
137         }
138 
139         int size = 0;
140         while (value > sizeTable[size])
141             size++;
142 
143         size++;
144         if (size >= buf.length - count) {
145             flushBuffer();
146         }
147 
148         int q, r;
149         int charPos = count + size;
150 
151         while (value >= 65536) {
152             q = value / 100;
153             r = value - ((q << 6) + (q << 5) + (q << 2));
154             value = q;
155             buf[--charPos] = DigitOnes[r];
156             buf[--charPos] = DigitTens[r];
157         }
158 
159         for (;;) {
160             q = (value * 52429) >>> (16 + 3);
161             r = value - ((q << 3) + (q << 1));
162             buf[--charPos] = digits[r];
163             value = q;
164             if (value == 0)
165                 break;
166         }
167         count += size;
168 
169         writeCrLf();
170     }
171 
172     override void flush() {
173         flushBuffer();
174         outputStream.flush();
175     }
176 }