Merge lp:~pbeaman/akiban-persistit/improve-KeyCoder into lp:akiban-persistit

Proposed by Peter Beaman
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
Reviewer Review Type Date Requested Status
Peter Beaman Approve
Nathan Williams Approve
Review via email: mp+116384@code.launchpad.net

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#appendByKeyCoder does not modify the serialized form; otherwise it escapes bytes having values of 0 or 1.

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.

To post a comment you must log in.
Revision history for this message
Peter Beaman (pbeaman) wrote :

The server branch lp:~pbeaman/akiban-server/improve-KeyCoder must be merged first.

Revision history for this message
Nathan Williams (nwilliams) wrote :

computeBogusHash() looks unused, but otherwise looks as described.

Will wait until server side goes into Approve.

review: Approve
342. By Peter Beaman

Remove computeBogusHash

Revision history for this message
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.

review: Needs Fixing
343. By Peter Beaman

Bump version number

Revision history for this message
Peter Beaman (pbeaman) wrote :

Bumped version to 3.1.4. Approving now since all other changes were already approved.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 }

Subscribers

People subscribed via source and target branches