515572ca819606c38ec97c84dc66f7a9a8e0a453
[controller.git] / atomix-storage / src / main / java / io / atomix / utils / serializer / ByteBufferInput.java
1 /* Copyright (c) 2008, Nathan Sweet
2  * All rights reserved.
3  * 
4  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
5  * conditions are met:
6  * 
7  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
8  * - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
9  * disclaimer in the documentation and/or other materials provided with the distribution.
10  * - Neither the name of Esoteric Software nor the names of its contributors may be used to endorse or promote products derived
11  * from this software without specific prior written permission.
12  * 
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
14  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
15  * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
16  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
17  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
18  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
19
20 package io.atomix.utils.serializer;
21
22 import java.nio.ByteBuffer;
23
24 /**
25  * A Kryo-4.0.3 ByteBufferInput adapted to deal with
26  * <a href="https://github.com/EsotericSoftware/kryo/issues/505">issue 505</a>.
27  *
28  * @author Roman Levenstein &lt;romixlev@gmail.com&gt;
29  * @author Robert Varga
30  */
31 public class ByteBufferInput extends com.esotericsoftware.kryo.io.ByteBufferInput {
32         public ByteBufferInput (ByteBuffer buffer) {
33                 super(buffer);
34         }
35
36         @Override
37         public String readString () {
38                 niobuffer.position(position);
39                 int available = require(1);
40                 position++;
41                 int b = niobuffer.get();
42                 if ((b & 0x80) == 0) return readAscii(); // ASCII.
43                 // Null, empty, or UTF8.
44                 int charCount = available >= 5 ? readUtf8Length(b) : readUtf8Length_slow(b);
45                 switch (charCount) {
46                 case 0:
47                         return null;
48                 case 1:
49                         return "";
50                 }
51                 charCount--;
52                 if (chars.length < charCount) chars = new char[charCount];
53                 readUtf8(charCount);
54                 return new String(chars, 0, charCount);
55         }
56
57         private int readUtf8Length (int b) {
58                 int result = b & 0x3F; // Mask all but first 6 bits.
59                 if ((b & 0x40) != 0) { // Bit 7 means another byte, bit 8 means UTF8.
60                         position++;
61                         b = niobuffer.get();
62                         result |= (b & 0x7F) << 6;
63                         if ((b & 0x80) != 0) {
64                                 position++;
65                                 b = niobuffer.get();
66                                 result |= (b & 0x7F) << 13;
67                                 if ((b & 0x80) != 0) {
68                                         position++;
69                                         b = niobuffer.get();
70                                         result |= (b & 0x7F) << 20;
71                                         if ((b & 0x80) != 0) {
72                                                 position++;
73                                                 b = niobuffer.get();
74                                                 result |= (b & 0x7F) << 27;
75                                         }
76                                 }
77                         }
78                 }
79                 return result;
80         }
81
82         private int readUtf8Length_slow (int b) {
83                 int result = b & 0x3F; // Mask all but first 6 bits.
84                 if ((b & 0x40) != 0) { // Bit 7 means another byte, bit 8 means UTF8.
85                         require(1);
86                         position++;
87                         b = niobuffer.get();
88                         result |= (b & 0x7F) << 6;
89                         if ((b & 0x80) != 0) {
90                                 require(1);
91                                 position++;
92                                 b = niobuffer.get();
93                                 result |= (b & 0x7F) << 13;
94                                 if ((b & 0x80) != 0) {
95                                         require(1);
96                                         position++;
97                                         b = niobuffer.get();
98                                         result |= (b & 0x7F) << 20;
99                                         if ((b & 0x80) != 0) {
100                                                 require(1);
101                                                 position++;
102                                                 b = niobuffer.get();
103                                                 result |= (b & 0x7F) << 27;
104                                         }
105                                 }
106                         }
107                 }
108                 return result;
109         }
110
111         private void readUtf8 (int charCount) {
112                 char[] chars = this.chars;
113                 // Try to read 7 bit ASCII chars.
114                 int charIndex = 0;
115                 int count = Math.min(require(1), charCount);
116                 int position = this.position;
117                 int b;
118                 while (charIndex < count) {
119                         position++;
120                         b = niobuffer.get();
121                         if (b < 0) {
122                                 position--;
123                                 break;
124                         }
125                         chars[charIndex++] = (char)b;
126                 }
127                 this.position = position;
128                 // If buffer didn't hold all chars or any were not ASCII, use slow path for remainder.
129                 if (charIndex < charCount) {
130                         niobuffer.position(position);
131                         readUtf8_slow(charCount, charIndex);
132                 }
133         }
134
135         private void readUtf8_slow (int charCount, int charIndex) {
136                 char[] chars = this.chars;
137                 while (charIndex < charCount) {
138                         if (position == limit) require(1);
139                         position++;
140                         int b = niobuffer.get() & 0xFF;
141                         switch (b >> 4) {
142                         case 0:
143                         case 1:
144                         case 2:
145                         case 3:
146                         case 4:
147                         case 5:
148                         case 6:
149                         case 7:
150                                 chars[charIndex] = (char)b;
151                                 break;
152                         case 12:
153                         case 13:
154                                 if (position == limit) require(1);
155                                 position++;
156                                 chars[charIndex] = (char)((b & 0x1F) << 6 | niobuffer.get() & 0x3F);
157                                 break;
158                         case 14:
159                                 require(2);
160                                 position += 2;
161                                 int b2 = niobuffer.get();
162                                 int b3 = niobuffer.get();
163                                 chars[charIndex] = (char)((b & 0x0F) << 12 | (b2 & 0x3F) << 6 | b3 & 0x3F);
164                                 break;
165                         }
166                         charIndex++;
167                 }
168         }
169
170         private String readAscii () {
171                 int end = position;
172                 int start = end - 1;
173                 int limit = this.limit;
174                 int b;
175                 do {
176                         if (end == limit) return readAscii_slow();
177                         end++;
178                         b = niobuffer.get();
179                 } while ((b & 0x80) == 0);
180                 int count = end - start;
181                 byte[] tmp = new byte[count];
182                 niobuffer.position(start);
183                 niobuffer.get(tmp);
184                 tmp[count - 1] &= 0x7F;  // Mask end of ascii bit.
185                 String value = new String(tmp, 0, 0, count);
186                 position = end;
187                 niobuffer.position(position);
188                 return value;
189         }
190
191         private String readAscii_slow () {
192                 position--; // Re-read the first byte.
193                 // Copy chars currently in buffer.
194                 int charCount = limit - position;
195                 if (charCount > chars.length) chars = new char[charCount * 2];
196                 char[] chars = this.chars;
197                 for (int i = position, ii = 0, n = limit; i < n; i++, ii++)
198                         chars[ii] = (char)niobuffer.get(i);
199                 position = limit;
200                 // Copy additional chars one by one.
201                 while (true) {
202                         require(1);
203                         position++;
204                         int b = niobuffer.get();
205                         if (charCount == chars.length) {
206                                 char[] newChars = new char[charCount * 2];
207                                 System.arraycopy(chars, 0, newChars, 0, charCount);
208                                 chars = newChars;
209                                 this.chars = newChars;
210                         }
211                         if ((b & 0x80) == 0x80) {
212                                 chars[charCount++] = (char)(b & 0x7F);
213                                 break;
214                         }
215                         chars[charCount++] = (char)b;
216                 }
217                 return new String(chars, 0, charCount);
218         }
219
220         @Override
221         public StringBuilder readStringBuilder () {
222                 niobuffer.position(position);
223                 int available = require(1);
224                 position++;
225                 int b = niobuffer.get();
226                 if ((b & 0x80) == 0) return new StringBuilder(readAscii()); // ASCII.
227                 // Null, empty, or UTF8.
228                 int charCount = available >= 5 ? readUtf8Length(b) : readUtf8Length_slow(b);
229                 switch (charCount) {
230                 case 0:
231                         return null;
232                 case 1:
233                         return new StringBuilder("");
234                 }
235                 charCount--;
236                 if (chars.length < charCount) chars = new char[charCount];
237                 readUtf8(charCount);
238                 StringBuilder builder = new StringBuilder(charCount);
239                 builder.append(chars, 0, charCount);
240                 return builder;
241         }
242 }