1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.mvstore; |
7 | |
8 | import java.nio.ByteBuffer; |
9 | |
10 | /** |
11 | * An auto-resize buffer to write data into a ByteBuffer. |
12 | */ |
13 | public class WriteBuffer { |
14 | |
15 | private static final int MAX_REUSE_CAPACITY = 4 * 1024 * 1024; |
16 | |
17 | /** |
18 | * The minimum number of bytes to grow a buffer at a time. |
19 | */ |
20 | private static final int MIN_GROW = 1024 * 1024; |
21 | |
22 | private ByteBuffer reuse = ByteBuffer.allocate(MIN_GROW); |
23 | |
24 | private ByteBuffer buff = reuse; |
25 | |
26 | /** |
27 | * Write a variable size integer. |
28 | * |
29 | * @param x the value |
30 | * @return this |
31 | */ |
32 | public WriteBuffer putVarInt(int x) { |
33 | DataUtils.writeVarInt(ensureCapacity(5), x); |
34 | return this; |
35 | } |
36 | |
37 | /** |
38 | * Write a variable size long. |
39 | * |
40 | * @param x the value |
41 | * @return this |
42 | */ |
43 | public WriteBuffer putVarLong(long x) { |
44 | DataUtils.writeVarLong(ensureCapacity(10), x); |
45 | return this; |
46 | } |
47 | |
48 | /** |
49 | * Write the characters of a string in a format similar to UTF-8. |
50 | * |
51 | * @param s the string |
52 | * @param len the number of characters to write |
53 | * @return this |
54 | */ |
55 | public WriteBuffer putStringData(String s, int len) { |
56 | ByteBuffer b = ensureCapacity(3 * len); |
57 | for (int i = 0; i < len; i++) { |
58 | int c = s.charAt(i); |
59 | if (c < 0x80) { |
60 | b.put((byte) c); |
61 | } else if (c >= 0x800) { |
62 | b.put((byte) (0xe0 | (c >> 12))); |
63 | b.put((byte) (((c >> 6) & 0x3f))); |
64 | b.put((byte) (c & 0x3f)); |
65 | } else { |
66 | b.put((byte) (0xc0 | (c >> 6))); |
67 | b.put((byte) (c & 0x3f)); |
68 | } |
69 | } |
70 | return this; |
71 | } |
72 | |
73 | /** |
74 | * Put a byte. |
75 | * |
76 | * @param x the value |
77 | * @return this |
78 | */ |
79 | public WriteBuffer put(byte x) { |
80 | ensureCapacity(1).put(x); |
81 | return this; |
82 | } |
83 | |
84 | /** |
85 | * Put a character. |
86 | * |
87 | * @param x the value |
88 | * @return this |
89 | */ |
90 | public WriteBuffer putChar(char x) { |
91 | ensureCapacity(2).putChar(x); |
92 | return this; |
93 | } |
94 | |
95 | /** |
96 | * Put a short. |
97 | * |
98 | * @param x the value |
99 | * @return this |
100 | */ |
101 | public WriteBuffer putShort(short x) { |
102 | ensureCapacity(2).putShort(x); |
103 | return this; |
104 | } |
105 | |
106 | /** |
107 | * Put an integer. |
108 | * |
109 | * @param x the value |
110 | * @return this |
111 | */ |
112 | public WriteBuffer putInt(int x) { |
113 | ensureCapacity(4).putInt(x); |
114 | return this; |
115 | } |
116 | |
117 | /** |
118 | * Put a long. |
119 | * |
120 | * @param x the value |
121 | * @return this |
122 | */ |
123 | public WriteBuffer putLong(long x) { |
124 | ensureCapacity(8).putLong(x); |
125 | return this; |
126 | } |
127 | |
128 | /** |
129 | * Put a float. |
130 | * |
131 | * @param x the value |
132 | * @return this |
133 | */ |
134 | public WriteBuffer putFloat(float x) { |
135 | ensureCapacity(4).putFloat(x); |
136 | return this; |
137 | } |
138 | |
139 | /** |
140 | * Put a double. |
141 | * |
142 | * @param x the value |
143 | * @return this |
144 | */ |
145 | public WriteBuffer putDouble(double x) { |
146 | ensureCapacity(8).putDouble(x); |
147 | return this; |
148 | } |
149 | |
150 | /** |
151 | * Put a byte array. |
152 | * |
153 | * @param bytes the value |
154 | * @return this |
155 | */ |
156 | public WriteBuffer put(byte[] bytes) { |
157 | ensureCapacity(bytes.length).put(bytes); |
158 | return this; |
159 | } |
160 | |
161 | /** |
162 | * Put a byte array. |
163 | * |
164 | * @param bytes the value |
165 | * @param offset the source offset |
166 | * @param length the number of bytes |
167 | * @return this |
168 | */ |
169 | public WriteBuffer put(byte[] bytes, int offset, int length) { |
170 | ensureCapacity(length).put(bytes, offset, length); |
171 | return this; |
172 | } |
173 | |
174 | /** |
175 | * Put the contents of a byte buffer. |
176 | * |
177 | * @param src the source buffer |
178 | * @return this |
179 | */ |
180 | public WriteBuffer put(ByteBuffer src) { |
181 | ensureCapacity(buff.remaining()).put(src); |
182 | return this; |
183 | } |
184 | |
185 | /** |
186 | * Set the limit, possibly growing the buffer. |
187 | * |
188 | * @param newLimit the new limit |
189 | * @return this |
190 | */ |
191 | public WriteBuffer limit(int newLimit) { |
192 | ensureCapacity(newLimit - buff.position()).limit(newLimit); |
193 | return this; |
194 | } |
195 | |
196 | /** |
197 | * Get the capacity. |
198 | * |
199 | * @return the capacity |
200 | */ |
201 | public int capacity() { |
202 | return buff.capacity(); |
203 | } |
204 | |
205 | /** |
206 | * Set the position. |
207 | * |
208 | * @param newPosition the new position |
209 | * @return the new position |
210 | */ |
211 | public WriteBuffer position(int newPosition) { |
212 | buff.position(newPosition); |
213 | return this; |
214 | } |
215 | |
216 | /** |
217 | * Get the limit. |
218 | * |
219 | * @return the limit |
220 | */ |
221 | public int limit() { |
222 | return buff.limit(); |
223 | } |
224 | |
225 | /** |
226 | * Get the current position. |
227 | * |
228 | * @return the position |
229 | */ |
230 | public int position() { |
231 | return buff.position(); |
232 | } |
233 | |
234 | /** |
235 | * Copy the data into the destination array. |
236 | * |
237 | * @param dst the destination array |
238 | * @return this |
239 | */ |
240 | public WriteBuffer get(byte[] dst) { |
241 | buff.get(dst); |
242 | return this; |
243 | } |
244 | |
245 | /** |
246 | * Update an integer at the given index. |
247 | * |
248 | * @param index the index |
249 | * @param value the value |
250 | * @return this |
251 | */ |
252 | public WriteBuffer putInt(int index, int value) { |
253 | buff.putInt(index, value); |
254 | return this; |
255 | } |
256 | |
257 | /** |
258 | * Update a short at the given index. |
259 | * |
260 | * @param index the index |
261 | * @param value the value |
262 | * @return this |
263 | */ |
264 | public WriteBuffer putShort(int index, short value) { |
265 | buff.putShort(index, value); |
266 | return this; |
267 | } |
268 | |
269 | /** |
270 | * Clear the buffer after use. |
271 | * |
272 | * @return this |
273 | */ |
274 | public WriteBuffer clear() { |
275 | if (buff.limit() > MAX_REUSE_CAPACITY) { |
276 | buff = reuse; |
277 | } else if (buff != reuse) { |
278 | reuse = buff; |
279 | } |
280 | buff.clear(); |
281 | return this; |
282 | } |
283 | |
284 | /** |
285 | * Get the byte buffer. |
286 | * |
287 | * @return the byte buffer |
288 | */ |
289 | public ByteBuffer getBuffer() { |
290 | return buff; |
291 | } |
292 | |
293 | private ByteBuffer ensureCapacity(int len) { |
294 | if (buff.remaining() < len) { |
295 | grow(len); |
296 | } |
297 | return buff; |
298 | } |
299 | |
300 | private void grow(int len) { |
301 | ByteBuffer temp = buff; |
302 | int needed = len - temp.remaining(); |
303 | int grow = Math.max(needed, MIN_GROW); |
304 | // grow at least 50% of the current size |
305 | grow = Math.max(temp.capacity() / 2, grow); |
306 | int newCapacity = temp.capacity() + grow; |
307 | try { |
308 | buff = ByteBuffer.allocate(newCapacity); |
309 | } catch (OutOfMemoryError e) { |
310 | throw new OutOfMemoryError("Capacity: " + newCapacity); |
311 | } |
312 | temp.flip(); |
313 | buff.put(temp); |
314 | if (newCapacity <= MAX_REUSE_CAPACITY) { |
315 | reuse = buff; |
316 | } |
317 | } |
318 | |
319 | } |