Merge lp:~pbeaman/akiban-persistit/improve-KeyCoder into lp:akiban-persistit
- improve-KeyCoder
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Peter Beaman |
Approved revision: | 343 |
Merged at revision: | 340 |
Proposed branch: | lp:~pbeaman/akiban-persistit/improve-KeyCoder |
Merge into: | lp:akiban-persistit |
Diff against target: |
774 lines (+285/-148) 9 files modified
pom.xml (+1/-1) src/main/java/com/persistit/ClassIndex.java (+2/-1) src/main/java/com/persistit/DefaultObjectCoder.java (+7/-59) src/main/java/com/persistit/Key.java (+56/-52) src/main/java/com/persistit/encoding/KeyCoder.java (+28/-0) src/main/java/com/persistit/encoding/KeyDisplayer.java (+5/-0) src/main/java/com/persistit/encoding/KeyRenderer.java (+5/-0) src/main/java/com/persistit/exception/ConversionException.java (+2/-2) src/test/java/com/persistit/unit/KeyCoderTest1.java (+179/-33) |
To merge this branch: | bzr merge lp:~pbeaman/akiban-persistit/improve-KeyCoder |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Peter Beaman | Approve | ||
Nathan Williams | Approve | ||
Review via email: mp+116384@code.launchpad.net |
Commit message
Description of the change
Reduces the size of serialized objects written by a KeyCoder.
This branch modifies the encoding generated by KeyCoder objects (such as CString in Akiban Server) to reduce the number of bytes required. There are two main changes:
- The encoding of handles is usually reduced to two bytes to one
- KeyCoder now has a new method isZeroByteFree().
If isZeroByteFree() returns true then Key#appendByKey
These are small changes, but it is desirable to modify the encoding format to a more compact form now before a significant amount of data has accumulated with the old encoding format.
Peter Beaman (pbeaman) wrote : | # |
Nathan Williams (nwilliams) wrote : | # |
computeBogusHash() looks unused, but otherwise looks as described.
Will wait until server side goes into Approve.
- 342. By Peter Beaman
-
Remove computeBogusHash
Peter Beaman (pbeaman) wrote : | # |
Upon further reflection I suggest that we should not approve this without a version bump to avoid inconveniencing the server team. Will propose that shortly.
- 343. By Peter Beaman
-
Bump version number
Peter Beaman (pbeaman) wrote : | # |
Bumped version to 3.1.4. Approving now since all other changes were already approved.
Preview Diff
1 | === modified file 'pom.xml' |
2 | --- pom.xml 2012-07-13 20:09:41 +0000 |
3 | +++ pom.xml 2012-07-25 14:54:18 +0000 |
4 | @@ -4,7 +4,7 @@ |
5 | |
6 | <groupId>com.akiban</groupId> |
7 | <artifactId>akiban-persistit</artifactId> |
8 | - <version>3.1.3-SNAPSHOT</version> |
9 | + <version>3.1.4-SNAPSHOT</version> |
10 | <packaging>jar</packaging> |
11 | |
12 | <name>akiban-persistit</name> |
13 | |
14 | === modified file 'src/main/java/com/persistit/ClassIndex.java' |
15 | --- src/main/java/com/persistit/ClassIndex.java 2012-07-20 19:25:35 +0000 |
16 | +++ src/main/java/com/persistit/ClassIndex.java 2012-07-25 14:54:18 +0000 |
17 | @@ -58,6 +58,7 @@ |
18 | * @version 1.1 |
19 | */ |
20 | final class ClassIndex { |
21 | + final static int HANDLE_BASE = 64; |
22 | private final static int INITIAL_CAPACITY = 123; |
23 | |
24 | private final static String BY_HANDLE = "byHandle"; |
25 | @@ -283,7 +284,7 @@ |
26 | // Store a new ClassInfo record |
27 | // |
28 | ex.clear().append(NEXT_ID).fetch(); |
29 | - handle = Math.max(_testIdFloor, value.isDefined() ? value.getInt() + 1 : 65); |
30 | + handle = Math.max(_testIdFloor, value.isDefined() ? value.getInt() : HANDLE_BASE) + 1; |
31 | value.clear().put(handle); |
32 | ex.store(); |
33 | |
34 | |
35 | === modified file 'src/main/java/com/persistit/DefaultObjectCoder.java' |
36 | --- src/main/java/com/persistit/DefaultObjectCoder.java 2012-05-25 18:50:59 +0000 |
37 | +++ src/main/java/com/persistit/DefaultObjectCoder.java 2012-07-25 14:54:18 +0000 |
38 | @@ -436,33 +436,7 @@ |
39 | return _keyBuilder; |
40 | } |
41 | |
42 | - /** |
43 | - * <p> |
44 | - * Append a key segment derived from an object to a {@link Key}. This method |
45 | - * will be called only if this <code>KeyCoder</code> has been registered |
46 | - * with the current {@link CoderManager} to encode objects having the class |
47 | - * of the supplied object. |
48 | - * </p> |
49 | - * <p> |
50 | - * Upon completion of this method, the backing byte array of the |
51 | - * <code>Key</code> and its size should be updated to reflect the appended |
52 | - * key segment. Use the methods {@link Key#getEncodedBytes}, |
53 | - * {@link Key#getEncodedSize} and {@link Key#setEncodedSize} to manipulate |
54 | - * the byte array directly. More commonly, the implementation of this method |
55 | - * will simply append appropriate identifying fields within the object to |
56 | - * the key. |
57 | - * </p> |
58 | - * |
59 | - * @param key |
60 | - * The {@link Key} to append to |
61 | - * @param object |
62 | - * The object to encode |
63 | - * @param context |
64 | - * An arbitrary object that can optionally be supplied by the |
65 | - * application to convey an application-specific context for the |
66 | - * operation. (See {@link CoderContext}.) The default value is |
67 | - * <code>null</code>. |
68 | - */ |
69 | + |
70 | @Override |
71 | public void appendKeySegment(Key key, Object object, CoderContext context) throws ConversionException { |
72 | Accessor accessor = null; |
73 | @@ -479,38 +453,6 @@ |
74 | } |
75 | } |
76 | |
77 | - /** |
78 | - * <p> |
79 | - * Decode a key segment as an Object value. This method is invoked when |
80 | - * {@link Key#decode(Object)} attempts to decode a key segment that was |
81 | - * previously append by the {@link #appendKeySegment} method. Thus the |
82 | - * implementation of this method must precisely decode the same bytes that |
83 | - * were generated by the companion <code>appendKeySegment</code> method of |
84 | - * this <code>ValueCoder</code>. |
85 | - * </p> |
86 | - * <p> |
87 | - * This method will be called only if this <code>KeyCoder</code> is |
88 | - * registered with the current {@link CoderManager} to encode and decode |
89 | - * objects of the class that is actually represented in the <code>Key</code> |
90 | - * . The class with which the key was encoded is provided as an argument. |
91 | - * This permits one <code>KeyCoder</code> to handle multiple classes because |
92 | - * the implementation can dispatch to code appropriate for the particular |
93 | - * supplied class. The implementation should construct and return an Object |
94 | - * having the same class as the supplied class. |
95 | - * </p> |
96 | - * |
97 | - * @param key |
98 | - * The {@link Key} from which data should be decoded |
99 | - * @param clazz |
100 | - * The expected <code>Class</code> of the returned object |
101 | - * @param context |
102 | - * An arbitrary object that can optionally be supplied by the |
103 | - * application to convey an application-specific context for the |
104 | - * operation. (See {@link CoderContext}.) The default value is |
105 | - * <code>null</code>. |
106 | - * @throws ConversionException |
107 | - */ |
108 | - |
109 | @Override |
110 | public Object decodeKeySegment(Key key, Class clazz, CoderContext context) throws ConversionException { |
111 | if (clazz != getClientClass()) |
112 | @@ -527,6 +469,12 @@ |
113 | |
114 | return readResolve(instance); |
115 | } |
116 | + |
117 | + |
118 | + @Override |
119 | + public boolean isZeroByteFree() { |
120 | + return false; |
121 | + } |
122 | |
123 | /** |
124 | * <p> |
125 | |
126 | === modified file 'src/main/java/com/persistit/Key.java' |
127 | --- src/main/java/com/persistit/Key.java 2012-05-25 18:50:59 +0000 |
128 | +++ src/main/java/com/persistit/Key.java 2012-07-25 14:54:18 +0000 |
129 | @@ -889,6 +889,7 @@ |
130 | private int _depth; |
131 | private int _maxSize; |
132 | private long _generation; |
133 | + private boolean _inKeyCoder; |
134 | private Persistit _persistit; |
135 | |
136 | /** |
137 | @@ -2673,10 +2674,10 @@ |
138 | throw new ConversionException("Invalid String lead-in byte (" + type + ") at position " + _index |
139 | + " in key"); |
140 | } |
141 | - int size = unquoteNulls(index + 1); |
142 | + int size = unquoteNulls(index + 1, false); |
143 | byte[] result = new byte[size]; |
144 | System.arraycopy(_bytes, index + 1, result, 0, size); |
145 | - index += quoteNulls(index + 1, size) + 2; |
146 | + index += quoteNulls(index + 1, size, false) + 2; |
147 | return result; |
148 | } finally { |
149 | _index = index; |
150 | @@ -3119,7 +3120,7 @@ |
151 | } |
152 | |
153 | void testValidForAppend() { |
154 | - if (_size == 0 || _size > 1 && _bytes[_size - 1] == 0) |
155 | + if (_size == 0 || _size > 1 && _bytes[_size - 1] == 0 || _inKeyCoder) |
156 | return; |
157 | |
158 | int b = _bytes[_size - 1] & 0xFF; |
159 | @@ -3282,27 +3283,25 @@ |
160 | private Key appendNull() { |
161 | int size = _size; |
162 | _bytes[size++] = TYPE_NULL; |
163 | - |
164 | return endSegment(size); |
165 | } |
166 | |
167 | private Key appendByKeyCoder(Object object, Class<?> cl, KeyCoder coder, CoderContext context) { |
168 | int size = _size; |
169 | + boolean saveInKeyCoder = _inKeyCoder; |
170 | try { |
171 | int handle = _persistit.getClassIndex().lookupByClass(cl).getHandle(); |
172 | - |
173 | _size += encodeHandle(handle); |
174 | int begin = _size; |
175 | - byte saveByte = _bytes[begin - 1]; |
176 | - _bytes[begin - 1] = 0; |
177 | + _inKeyCoder = true; |
178 | coder.appendKeySegment(this, object, context); |
179 | - _bytes[begin - 1] = saveByte; |
180 | - quoteNulls(begin, _size - begin); |
181 | + quoteNulls(begin, _size - begin, coder.isZeroByteFree()); |
182 | endSegment(_size); |
183 | size = _size; |
184 | return this; |
185 | } finally { |
186 | _size = size; |
187 | + _inKeyCoder = saveInKeyCoder; |
188 | } |
189 | } |
190 | |
191 | @@ -3312,6 +3311,7 @@ |
192 | Class<?> clazz = Object.class; |
193 | int segmentSize = 0; |
194 | boolean unquoted = false; |
195 | + boolean zeroByteFree = false; |
196 | try { |
197 | int handle = decodeHandle(); |
198 | clazz = _persistit.classForHandle(handle); |
199 | @@ -3322,10 +3322,11 @@ |
200 | if (coder == null) { |
201 | throw new ConversionException("No KeyCoder for class " + clazz.getName()); |
202 | } |
203 | - segmentSize = unquoteNulls(index); |
204 | - unquoted = true; |
205 | + zeroByteFree = coder.isZeroByteFree(); |
206 | + segmentSize = unquoteNulls(index, zeroByteFree); |
207 | size = _size; |
208 | _size = index + segmentSize; |
209 | + unquoted = true; |
210 | if (target == null) { |
211 | return coder.decodeKeySegment(this, clazz, context); |
212 | } else { |
213 | @@ -3339,7 +3340,7 @@ |
214 | } finally { |
215 | _size = size; |
216 | if (unquoted) { |
217 | - index += quoteNulls(index, segmentSize); |
218 | + index += quoteNulls(index, segmentSize, zeroByteFree); |
219 | index = decodeEnd(index); |
220 | } |
221 | _index = index; |
222 | @@ -3352,6 +3353,7 @@ |
223 | Class<?> clazz; |
224 | int segmentSize = 0; |
225 | boolean unquoted = false; |
226 | + boolean zeroByteFree = false; |
227 | try { |
228 | int handle = decodeHandle(); |
229 | clazz = _persistit.classForHandle(handle); |
230 | @@ -3365,8 +3367,9 @@ |
231 | Util.append(sb, clazz.getName()); |
232 | Util.append(sb, ")"); |
233 | coder = _persistit.lookupKeyCoder(clazz); |
234 | + zeroByteFree = coder.isZeroByteFree(); |
235 | } |
236 | - segmentSize = unquoteNulls(index); |
237 | + segmentSize = unquoteNulls(index, zeroByteFree); |
238 | size = _size; |
239 | unquoted = true; |
240 | |
241 | @@ -3385,7 +3388,7 @@ |
242 | } finally { |
243 | _size = size; |
244 | if (unquoted) { |
245 | - index += quoteNulls(index, segmentSize); |
246 | + index += quoteNulls(index, segmentSize, zeroByteFree); |
247 | index = decodeEnd(index); |
248 | } |
249 | _index = index; |
250 | @@ -3462,7 +3465,7 @@ |
251 | int keySize = _size; |
252 | System.arraycopy(bytes, offset, _bytes, keySize, size); |
253 | _size += size; |
254 | - keySize += quoteNulls(keySize, size); |
255 | + keySize += quoteNulls(keySize, size, false); |
256 | return endSegment(keySize); |
257 | } |
258 | |
259 | @@ -3552,31 +3555,33 @@ |
260 | } |
261 | |
262 | private int encodeHandle(int handle) { |
263 | + |
264 | + int v = handle - ClassIndex.HANDLE_BASE; |
265 | int size = _size; |
266 | |
267 | - if (handle < 0x00000007) { |
268 | - _bytes[size++] = (byte) (TYPE_CODER1 | ((handle) & 0x7)); |
269 | + if (v < 0x00000007) { |
270 | + _bytes[size++] = (byte) (TYPE_CODER1 | ((v) & 0x7)); |
271 | return 1; |
272 | } |
273 | |
274 | - if (handle < 0x000001FF) { |
275 | - _bytes[size++] = (byte) (TYPE_CODER2 | ((handle >>> 6) & 0x7)); |
276 | - _bytes[size++] = (byte) (0x80 | ((handle) & 0x3F)); |
277 | + if (v < 0x000001FF) { |
278 | + _bytes[size++] = (byte) (TYPE_CODER2 | ((v >>> 6) & 0x7)); |
279 | + _bytes[size++] = (byte) (0x80 | ((v) & 0x3F)); |
280 | return 2; |
281 | } |
282 | |
283 | - if (handle < 0x00007FFF) { |
284 | - _bytes[size++] = (byte) (TYPE_CODER3 | ((handle >>> 12) & 0x7)); |
285 | - _bytes[size++] = (byte) (0xC0 | ((handle >>> 6) & 0x3F)); |
286 | - _bytes[size++] = (byte) (0x80 | ((handle) & 0x3F)); |
287 | + if (v < 0x00007FFF) { |
288 | + _bytes[size++] = (byte) (TYPE_CODER3 | ((v >>> 12) & 0x7)); |
289 | + _bytes[size++] = (byte) (0xC0 | ((v >>> 6) & 0x3F)); |
290 | + _bytes[size++] = (byte) (0x80 | ((v) & 0x3F)); |
291 | return 3; |
292 | } else { |
293 | - _bytes[size++] = (byte) (TYPE_CODER6 | ((handle >>> 30) & 0x7)); |
294 | - _bytes[size++] = (byte) (0xC0 | ((handle >>> 24) & 0x3F)); |
295 | - _bytes[size++] = (byte) (0xC0 | ((handle >>> 18) & 0x3F)); |
296 | - _bytes[size++] = (byte) (0xC0 | ((handle >>> 12) & 0x3F)); |
297 | - _bytes[size++] = (byte) (0xC0 | ((handle >>> 6) & 0x3F)); |
298 | - _bytes[size++] = (byte) (0x80 | ((handle) & 0x3F)); |
299 | + _bytes[size++] = (byte) (TYPE_CODER6 | ((v >>> 30) & 0x7)); |
300 | + _bytes[size++] = (byte) (0xC0 | ((v >>> 24) & 0x3F)); |
301 | + _bytes[size++] = (byte) (0xC0 | ((v >>> 18) & 0x3F)); |
302 | + _bytes[size++] = (byte) (0xC0 | ((v >>> 12) & 0x3F)); |
303 | + _bytes[size++] = (byte) (0xC0 | ((v >>> 6) & 0x3F)); |
304 | + _bytes[size++] = (byte) (0x80 | ((v) & 0x3F)); |
305 | return 6; |
306 | } |
307 | } |
308 | @@ -3617,22 +3622,15 @@ |
309 | |
310 | case TYPE_CODER2: |
311 | v = _bytes[index++] & 0xFF; |
312 | - if ((v & 0xC0) != 0xC0) |
313 | + if ((v & 0x80) != 0x80) |
314 | break; |
315 | result = result << 6 | (v & 0x3F); |
316 | |
317 | // Intentionally falls through |
318 | |
319 | case TYPE_CODER1: |
320 | - v = _bytes[index++] & 0xFF; |
321 | - if ((v & 0xC0) != 0x80) |
322 | - break; |
323 | - result = result << 6 | (v & 0x3F); |
324 | - |
325 | - // Intentionally falls through |
326 | - |
327 | _index = index; |
328 | - return result; |
329 | + return result + ClassIndex.HANDLE_BASE; |
330 | |
331 | default: |
332 | } |
333 | @@ -3674,21 +3672,27 @@ |
334 | * (1, C) where C is 32 for NUL and 33 for SOH. |
335 | * |
336 | * @param index |
337 | - * Offet to first byte to convert |
338 | + * Offset to first byte to convert |
339 | * @param size |
340 | * Length of raw segment, in bytes. |
341 | * @return Length of the quoted segment |
342 | */ |
343 | - private int quoteNulls(int index, int size) { |
344 | + private int quoteNulls(int index, int size, boolean zeroByteFree) { |
345 | for (int i = index; i < index + size; i++) { |
346 | int c = _bytes[i] & 0xFF; |
347 | - if (c == 0 || c == 1) { |
348 | - System.arraycopy(_bytes, i + 1, _bytes, i + 2, _size - i - 1); |
349 | - _bytes[i] = 1; |
350 | - _bytes[i + 1] = (byte) (c + 32); |
351 | - _size++; |
352 | - size++; |
353 | - i++; |
354 | + if (zeroByteFree) { |
355 | + if (c == 0) { |
356 | + throw new ConversionException("NUL found in encoded Key"); |
357 | + } |
358 | + } else { |
359 | + if (c == 0 || c == 1) { |
360 | + System.arraycopy(_bytes, i + 1, _bytes, i + 2, _size - i - 1); |
361 | + _bytes[i] = 1; |
362 | + _bytes[i + 1] = (byte) (c + 32); |
363 | + _size++; |
364 | + size++; |
365 | + i++; |
366 | + } |
367 | } |
368 | } |
369 | return size; |
370 | @@ -3702,12 +3706,13 @@ |
371 | * @param index |
372 | * @return The unquoted length of the array. |
373 | */ |
374 | - private int unquoteNulls(int index) { |
375 | + private int unquoteNulls(int index, boolean zeroByteFree) { |
376 | for (int i = index; i < _size; i++) { |
377 | int c = _bytes[i] & 0xFF; |
378 | - if (c == 0) |
379 | + if (c == 0) { |
380 | return i - index; |
381 | - if (c == 1 && i + 1 < _size) { |
382 | + } |
383 | + if (!zeroByteFree && c == 1 && i + 1 < _size) { |
384 | c = _bytes[i + 1]; |
385 | if (c == 32 || c == 33) { |
386 | _bytes[i] = (byte) (c - 32); |
387 | @@ -3997,4 +4002,3 @@ |
388 | bis._scale = -exp; |
389 | } |
390 | } |
391 | - |
392 | |
393 | === modified file 'src/main/java/com/persistit/encoding/KeyCoder.java' |
394 | --- src/main/java/com/persistit/encoding/KeyCoder.java 2012-05-25 18:50:59 +0000 |
395 | +++ src/main/java/com/persistit/encoding/KeyCoder.java 2012-07-25 14:54:18 +0000 |
396 | @@ -95,6 +95,11 @@ |
397 | * supplied class. The implementation should construct and return an Object |
398 | * having the same class as the supplied class. |
399 | * </p> |
400 | + * <p> |
401 | + * When this method is called the value {@link Key#getIndex()} will be the |
402 | + * offset within the key of the first encoded byte. The key segment is |
403 | + * zero-byte terminated. |
404 | + * </p> |
405 | * |
406 | * @param key |
407 | * The {@link Key} from which data should be decoded |
408 | @@ -109,4 +114,27 @@ |
409 | */ |
410 | |
411 | public Object decodeKeySegment(Key key, Class<?> clazz, CoderContext context) throws ConversionException; |
412 | + |
413 | + /** |
414 | + * <p> |
415 | + * Since a key segment is terminated by a zero byte value the encoded |
416 | + * portion of the segment must not contain zeroes. This method should |
417 | + * indicate whether the encoding implemented by this KeyCoder produces zero |
418 | + * bytes. |
419 | + * </p> |
420 | + * <p> |
421 | + * If this method returns <code>false</code> then Persistit will insert |
422 | + * escape sequences into the encoding produced by |
423 | + * {@link #appendKeySegment(Key, Object, CoderContext)} to a form with no |
424 | + * zeroes. |
425 | + * </p> |
426 | + * <p> |
427 | + * If this method returns <code>true</code> then Persistit will verify that |
428 | + * there are no zero bytes and throw an ConversionException otherwise. |
429 | + * |
430 | + * @return whether this KeyCoder produces encodings guaranteed not to |
431 | + * contain zero bytes |
432 | + * @throws ConversionException |
433 | + */ |
434 | + public boolean isZeroByteFree() throws ConversionException; |
435 | } |
436 | |
437 | === modified file 'src/main/java/com/persistit/encoding/KeyDisplayer.java' |
438 | --- src/main/java/com/persistit/encoding/KeyDisplayer.java 2012-05-25 18:50:59 +0000 |
439 | +++ src/main/java/com/persistit/encoding/KeyDisplayer.java 2012-07-25 14:54:18 +0000 |
440 | @@ -51,6 +51,11 @@ |
441 | * call this method to decode a value that was <code>null</code> when |
442 | * written because null values are handled by built-in encoding logic. |
443 | * </p> |
444 | + * <p> |
445 | + * When this method is called the value {@link Key#getIndex()} will be the |
446 | + * offset within the key of the first encoded byte. The key segment is |
447 | + * zero-byte terminated. |
448 | + * </p> |
449 | * |
450 | * @param key |
451 | * The <code>Key</code> from which interior fields of the object |
452 | |
453 | === modified file 'src/main/java/com/persistit/encoding/KeyRenderer.java' |
454 | --- src/main/java/com/persistit/encoding/KeyRenderer.java 2012-05-25 18:50:59 +0000 |
455 | +++ src/main/java/com/persistit/encoding/KeyRenderer.java 2012-07-25 14:54:18 +0000 |
456 | @@ -50,6 +50,11 @@ |
457 | * call this method to decode a value that was <code>null</code> when |
458 | * written because null values are handled by built-in encoding logic. |
459 | * </p> |
460 | + * <p> |
461 | + * When this method is called the value {@link Key#getIndex()} will be the |
462 | + * offset within the key of the first encoded byte. The key segment is |
463 | + * zero-byte terminated. |
464 | + * </p> |
465 | * |
466 | * @param key |
467 | * The <code>Key</code> from which interior fields of the object |
468 | |
469 | === modified file 'src/main/java/com/persistit/exception/ConversionException.java' |
470 | --- src/main/java/com/persistit/exception/ConversionException.java 2012-05-25 18:50:59 +0000 |
471 | +++ src/main/java/com/persistit/exception/ConversionException.java 2012-07-25 14:54:18 +0000 |
472 | @@ -22,8 +22,8 @@ |
473 | |
474 | /** |
475 | * Thrown by encoding and decoding methods of the {@link com.persistit.Value} |
476 | - * class when conversion to or from an value's serialized form cannot be |
477 | - * completed. |
478 | + * and {@link com.persistit.Key} classes when conversion to or from an value's |
479 | + * serialized form cannot be completed. |
480 | * |
481 | * @version 1.0 |
482 | */ |
483 | |
484 | === modified file 'src/test/java/com/persistit/unit/KeyCoderTest1.java' |
485 | --- src/test/java/com/persistit/unit/KeyCoderTest1.java 2012-05-25 18:50:59 +0000 |
486 | +++ src/test/java/com/persistit/unit/KeyCoderTest1.java 2012-07-25 14:54:18 +0000 |
487 | @@ -21,7 +21,9 @@ |
488 | package com.persistit.unit; |
489 | |
490 | import static org.junit.Assert.assertEquals; |
491 | +import static org.junit.Assert.assertTrue; |
492 | |
493 | +import java.io.Serializable; |
494 | import java.net.MalformedURLException; |
495 | import java.net.URL; |
496 | |
497 | @@ -33,35 +35,34 @@ |
498 | import com.persistit.TestShim; |
499 | import com.persistit.encoding.CoderContext; |
500 | import com.persistit.encoding.KeyCoder; |
501 | +import com.persistit.encoding.KeyDisplayer; |
502 | import com.persistit.encoding.KeyRenderer; |
503 | import com.persistit.exception.ConversionException; |
504 | import com.persistit.exception.PersistitException; |
505 | +import com.persistit.util.Util; |
506 | |
507 | public class KeyCoderTest1 extends PersistitUnitTestCase { |
508 | |
509 | @Test |
510 | - public void test2() throws MalformedURLException { |
511 | - System.out.print("test2 "); |
512 | - final KeyCoder coder = new TestKeyRenderer(); |
513 | + public void normalKeyCoder() throws MalformedURLException { |
514 | + final KeyCoder coder = new TestKeyRenderer1(); |
515 | _persistit.getCoderManager().registerKeyCoder(URL.class, coder); |
516 | - Key key1; |
517 | - |
518 | - key1 = new Key(_persistit); |
519 | + Key key = new Key(_persistit); |
520 | final URL url1 = new URL("http://w/z"); |
521 | final URL url2 = new URL("http://w:8080/z?userid=pb"); |
522 | - key1.clear(); |
523 | - key1.append("a"); |
524 | - key1.append(url1); |
525 | - key1.append("b"); |
526 | - key1.append(url2); |
527 | - key1.append("c"); |
528 | + key.clear(); |
529 | + key.append("a"); |
530 | + key.append(url1); |
531 | + key.append("b"); |
532 | + key.append(url2); |
533 | + key.append("c"); |
534 | |
535 | - key1.reset(); |
536 | - final String a = key1.decodeString(); |
537 | - final Object obj1 = key1.decode(); |
538 | - final String b = key1.decodeString(); |
539 | - final Object obj2 = key1.decode(); |
540 | - final String c = key1.decodeString(); |
541 | + key.reset(); |
542 | + final String a = key.decodeString(); |
543 | + final Object obj1 = key.decode(); |
544 | + final String b = key.decodeString(); |
545 | + final Object obj2 = key.decode(); |
546 | + final String c = key.decodeString(); |
547 | |
548 | assertEquals("a", a); |
549 | assertEquals("b", b); |
550 | @@ -70,26 +71,73 @@ |
551 | assertEquals(url2.toString(), obj2.toString()); |
552 | |
553 | final StringBuilder sb = new StringBuilder(); |
554 | - key1.indexTo(1); |
555 | - key1.decode(sb); |
556 | + key.indexTo(1); |
557 | + key.decode(sb); |
558 | assertEquals(url1.toString(), sb.toString()); |
559 | |
560 | sb.setLength(0); |
561 | - key1.indexTo(3); |
562 | - key1.decode(sb); |
563 | + key.indexTo(3); |
564 | + key.decode(sb); |
565 | assertEquals(url2.toString(), sb.toString()); |
566 | |
567 | - key1.reset(); |
568 | - final String toString = key1.toString(); |
569 | + key.reset(); |
570 | + final String toString = key.toString(); |
571 | assertEquals(toString, "{\"a\",(java.net.URL){\"http\",\"w\",-1,\"/z\"},\"b\"," |
572 | + "(java.net.URL){\"http\",\"w\",8080,\"/z?userid=pb\"},\"c\"}"); |
573 | - System.out.println("- done"); |
574 | + } |
575 | + |
576 | + @Test |
577 | + public void zeroByteFreeKeyCoder() throws Exception { |
578 | + final KeyCoder coder = new TestKeyRenderer2(); |
579 | + _persistit.getCoderManager().registerKeyCoder(WrappedString.class, coder); |
580 | + Key key = new Key(_persistit); |
581 | + key.append(1.2f); |
582 | + key.append(new WrappedString(RED_FOX)); |
583 | + key.append("another key"); |
584 | + key.append(new WrappedString("abcde")); |
585 | + key.reset(); |
586 | + assertEquals(Float.valueOf(1.2f),key.decode()); |
587 | + Object ws = key.decode(); |
588 | + assertTrue(ws instanceof WrappedString); |
589 | + assertEquals(RED_FOX, ((WrappedString)ws)._value); |
590 | + assertEquals("another key", key.decodeString()); |
591 | + key.decode(ws); |
592 | + assertEquals("abcde", ((WrappedString)ws)._value); |
593 | + } |
594 | + |
595 | + @Test |
596 | + public void normalKeyCoderQuotesNulls() throws Exception { |
597 | + final KeyCoder coder = new TestKeyRenderer3(); |
598 | + _persistit.getCoderManager().registerKeyCoder(WrappedLong.class, coder); |
599 | + Key key = new Key(_persistit); |
600 | + for (long value = 0; value < 32; value++) { |
601 | + key.append(new WrappedLong(value)); |
602 | + } |
603 | + key.reset(); |
604 | + for (int depth = 0; depth < 50; depth++) { |
605 | + key.indexTo(depth); |
606 | + if (key.getIndex() == key.getEncodedSize()) { |
607 | + assertEquals(32, depth); |
608 | + break; |
609 | + } |
610 | + long value = ((WrappedLong)key.decode())._value; |
611 | + assertEquals(depth, value); |
612 | + } |
613 | + } |
614 | + |
615 | + @Test(expected = ConversionException.class) |
616 | + public void zeroByteFreeKeyCoderThrows() throws Exception { |
617 | + final KeyCoder coder = new TestKeyRenderer2(); |
618 | + _persistit.getCoderManager().registerKeyCoder(WrappedString.class, coder); |
619 | + Key key = new Key(_persistit); |
620 | + WrappedString ws = new WrappedString(new String(new byte[]{'a', 'b', 0, 'c'})); |
621 | + key.append(ws); |
622 | } |
623 | |
624 | @Test |
625 | - public void testKeyHandleEncodeDecode() throws Exception { |
626 | - final int[] handles = { 0, 0x1FE, 0x1FF, 0x200, 0x201, 0x7FFE, 0x7FFF, 0x8000, 0x8001, Integer.MAX_VALUE }; |
627 | - final KeyCoder coder = new TestKeyRenderer(); |
628 | + public void keyHandleEncodeDecode() throws Exception { |
629 | + final int[] handles = { 64, 0x1FE, 0x1FF, 0x200, 0x201, 0x7FFE, 0x7FFF, 0x8000, 0x8001, Integer.MAX_VALUE }; |
630 | + final KeyCoder coder = new TestKeyRenderer1(); |
631 | final Key key1 = new Key(_persistit); |
632 | final URL url1 = new URL("http://w/z"); |
633 | final URL url2 = new URL("http://w:8080/z?userid=pb"); |
634 | @@ -115,7 +163,7 @@ |
635 | |
636 | } |
637 | } |
638 | - |
639 | + |
640 | private void registerCoderDefinedKeyHandle(final KeyCoder coder, final int handle) throws PersistitException { |
641 | TestShim.clearAllClassIndexEntries(_persistit); |
642 | TestShim.setClassIndexTestIdFloor(_persistit, handle); |
643 | @@ -130,7 +178,7 @@ |
644 | Assert.assertTrue(condition); |
645 | } |
646 | |
647 | - public static class TestKeyRenderer implements KeyRenderer { |
648 | + public static class TestKeyRenderer1 implements KeyRenderer { |
649 | |
650 | @Override |
651 | public void appendKeySegment(final Key key, final Object object, final CoderContext context) { |
652 | @@ -142,7 +190,7 @@ |
653 | } |
654 | |
655 | @Override |
656 | - public Object decodeKeySegment(final Key key, final Class cl, final CoderContext context) { |
657 | + public Object decodeKeySegment(final Key key, final Class<?> cl, final CoderContext context) { |
658 | final String protocol = key.decodeString(); |
659 | final String host = key.decodeString(); |
660 | final int port = key.decodeInt(); |
661 | @@ -155,7 +203,7 @@ |
662 | } |
663 | |
664 | @Override |
665 | - public void renderKeySegment(final Key key, final Object target, final Class cl, final CoderContext context) { |
666 | + public void renderKeySegment(final Key key, final Object target, final Class<?> cl, final CoderContext context) { |
667 | final StringBuilder sb = (StringBuilder) target; |
668 | key.decodeString(sb); |
669 | sb.append("://"); |
670 | @@ -167,5 +215,103 @@ |
671 | } |
672 | key.decodeString(sb); |
673 | } |
674 | - } |
675 | + |
676 | + @Override |
677 | + public boolean isZeroByteFree() { |
678 | + return false; |
679 | + } |
680 | + } |
681 | + |
682 | + @SuppressWarnings("serial") |
683 | + public static class WrappedString implements Serializable { |
684 | + String _value; |
685 | + WrappedString(final String value) { |
686 | + _value = value; |
687 | + } |
688 | + } |
689 | + |
690 | + @SuppressWarnings("serial") |
691 | + public static class WrappedLong implements Serializable { |
692 | + long _value; |
693 | + WrappedLong(final long value) { |
694 | + _value = value; |
695 | + } |
696 | + } |
697 | + |
698 | + |
699 | + public static class TestKeyRenderer2 implements KeyRenderer { |
700 | + |
701 | + @Override |
702 | + public void appendKeySegment(final Key key, final Object object, final CoderContext context) { |
703 | + final WrappedString cstring = (WrappedString) object; |
704 | + byte[] bytes = cstring._value.getBytes(); |
705 | + int size = key.getEncodedSize(); |
706 | + System.arraycopy(bytes, 0, key.getEncodedBytes(), size, bytes.length); |
707 | + key.setEncodedSize(size + bytes.length); |
708 | + } |
709 | + |
710 | + @Override |
711 | + public Object decodeKeySegment(final Key key, final Class<?> cl, final CoderContext context) { |
712 | + WrappedString target = new WrappedString(null); |
713 | + renderKeySegment(key, target, cl, context); |
714 | + return target; |
715 | + } |
716 | + |
717 | + @Override |
718 | + public void renderKeySegment(final Key key, final Object target, final Class<?> cl, final CoderContext context) { |
719 | + WrappedString cstring = (WrappedString)target; |
720 | + int size = key.getEncodedSize(); |
721 | + int index = key.getIndex(); |
722 | + byte[] b = new byte[size - index]; |
723 | + System.arraycopy(key.getEncodedBytes(), index, b, 0, b.length); |
724 | + cstring._value = new String(b); |
725 | + } |
726 | + |
727 | + @Override |
728 | + public boolean isZeroByteFree() { |
729 | + return true; |
730 | + } |
731 | + } |
732 | + |
733 | + public static class TestKeyRenderer3 implements KeyRenderer, KeyDisplayer { |
734 | + |
735 | + @Override |
736 | + public void appendKeySegment(final Key key, final Object object, final CoderContext context) { |
737 | + final WrappedLong wlong = (WrappedLong)object; |
738 | + int size = key.getEncodedSize(); |
739 | + Util.putLong(key.getEncodedBytes(), size, wlong._value); |
740 | + key.setEncodedSize(size + 8); |
741 | + } |
742 | + |
743 | + @Override |
744 | + public Object decodeKeySegment(final Key key, final Class<?> cl, final CoderContext context) { |
745 | + WrappedLong target = new WrappedLong(Long.MIN_VALUE); |
746 | + renderKeySegment(key, target, cl, context); |
747 | + return target; |
748 | + } |
749 | + |
750 | + @Override |
751 | + public void renderKeySegment(final Key key, final Object target, final Class<?> cl, final CoderContext context) { |
752 | + WrappedLong wl = (WrappedLong)target; |
753 | + int size = key.getEncodedSize(); |
754 | + int index = key.getIndex(); |
755 | + assertEquals(8, size - index); |
756 | + wl._value = Util.getLong(key.getEncodedBytes(), index); |
757 | + key.setIndex(index + 8); |
758 | + } |
759 | + |
760 | + @Override |
761 | + public boolean isZeroByteFree() { |
762 | + return false; |
763 | + } |
764 | + |
765 | + @Override |
766 | + public void displayKeySegment(Key key, Appendable target, Class<?> clazz, CoderContext context) |
767 | + throws ConversionException { |
768 | + WrappedLong wl = new WrappedLong(Long.MIN_VALUE); |
769 | + renderKeySegment(key, wl, clazz, context); |
770 | + Util.append(target, Long.toString(wl._value)); |
771 | + } |
772 | + } |
773 | + |
774 | } |
The server branch lp:~pbeaman/akiban-server/improve-KeyCoder must be merged first.