Custom Typehandler Example

For a custom typehandler example we will try to write a very simple typehandler for StringBuffer(java) and StringBuilder(.NET). These classes are basically just another representation of String, so we can look at the StringHandler implementation in db4o source.

To keep it simple we will skip information required for indexing - please look at IndexableTypeHandler in db4o sources to get more information on how to handle indexes.

The first thing should be #write method, which determines how the object is persisted:

StringBufferHandler.java: write
1public void write(WriteContext context, Object obj) { 2 String str = ((StringBuffer)obj).toString(); 3 WriteBuffer buffer = context; 4 buffer.writeInt(str.length()); 5 writeToBuffer(buffer, str); 6 }
StringBufferHandler.java: writeToBuffer
1private static void writeToBuffer(WriteBuffer buffer, String str){ 2 final int length = str.length(); 3 char[] chars = new char[length]; 4 str.getChars(0, length, chars, 0); 5 for (int i = 0; i < length; i ++){ 6 buffer.writeByte((byte) (chars[i] & 0xff)); 7 buffer.writeByte((byte) (chars[i] >> 8)); 8 } 9 }

As you can see from the code above, there are 3 steps:

  1. Get the buffer from WriteContext/I WriteContext
  2. Write the length of the StringBuffer/StringBuilder
  3. Transfer the object to char array and write them in Unicode

Next step is to read the same from the buffer. It is just opposite to the write method:

StringBufferHandler.java: read
1public Object read(ReadContext context) { 2 ReadBuffer buffer = context; 3 String str = ""; 4 int length = buffer.readInt(); 5 if (length > 0) { 6 str = readBuffer(buffer, length); 7 } 8 return new StringBuffer(str); 9 }
StringBufferHandler.java: readBuffer
1private static String readBuffer(ReadBuffer buffer, int length){ 2 char[] chars = new char[length]; 3 for(int ii = 0; ii < length; ii++){ 4 chars[ii] = (char)((buffer.readByte() & 0xff) | ((buffer.readByte() & 0xff) << 8)); 5 } 6 return new String(chars, 0, length); 7 }

Delete is simple - we just reposition the buffer offset to the end of the slot:

StringBufferHandler.java: delete
1public void delete(DeleteContext context) { 2 context.readSlot(); 3 }

Try to experiment with the #delete method by implementing cascade on delete. Use FirstClassObjectHandler as an example.

We are done with the read/write operations. But as you remember, in order to read an object, we must find it through a query, and that's where we will need a #compare method  (well, you do not need it if your query does not contain any comparison criteria, but this is normally not the case):

StringBufferHandler.java: prepareComparison
1public PreparedComparison prepareComparison(Context ctx, final Object obj) { 2 return new PreparedComparison() { 3 public int compareTo(Object target) { 4 return compare((StringBuffer)obj, (StringBuffer)target); 5 } 6 }; 7 }
StringBufferHandler.java: compare
01private final int compare(StringBuffer a_compare, StringBuffer a_with) { 02 if (a_compare == null) { 03 if (a_with == null) { 04 return 0; 05 } 06 return -1; 07 } 08 if (a_with == null) { 09 return 1; 10 } 11 char c_compare[] = new char[a_compare.length()]; 12 a_compare.getChars(0, a_compare.length() - 1, c_compare, 0); 13 char c_with[] = new char[a_with.length()]; 14 a_with.getChars(0, a_with.length() - 1, c_with, 0); 15 16 return compareChars(c_compare, c_with); 17 }
StringBufferHandler.java: compareChars
1private static final int compareChars(char[] compare, char[] with) { 2 int min = compare.length < with.length ? compare.length : with.length; 3 for (int i = 0; i < min; i++) { 4 if (compare[i] != with[i]) { 5 return compare[i] - with[i]; 6 } 7 } 8 return compare.length - with.length; 9 }

The last method left: #defragment. This one only moves the offset to the beginning of the object data, i.e. skips Id and size information (to be compatible to older versions):

StringBufferHandler.java: defragment
1public void defragment(DefragmentContext context) { 2 // To stay compatible with the old marshaller family 3 // In the marshaller family 0 number 8 represented 4 // length required to store ID and object length information 5 context.incrementOffset(8); 6 }

This Typehandler implementation can be tested with a class below. Please, pay special attention to #configure method, which adds StringBufferHandler/StringBuilderHandler to the database configuration:

TypehandlerExample.java
001package com.db4odoc.typehandler; 002 003import java.io.File; 004import java.io.IOException; 005 006import com.db4o.Db4o; 007import com.db4o.ObjectContainer; 008import com.db4o.ObjectSet; 009import com.db4o.config.Configuration; 010import com.db4o.defragment.Defragment; 011import com.db4o.ext.DatabaseFileLockedException; 012import com.db4o.query.Query; 013import com.db4o.reflect.ReflectClass; 014import com.db4o.reflect.generic.GenericReflector; 015import com.db4o.reflect.jdk.JdkReflector; 016import com.db4o.typehandlers.TypeHandlerPredicate; 017 018public class TypehandlerExample { 019 020 private final static String DB4O_FILE_NAME = "reference.db4o"; 021 private static ObjectContainer _container = null; 022 023 024 public static void main(String[] args) throws IOException { 025 testReadWriteDelete(); 026 //testDefrag(); 027 testCompare(); 028 } 029 // end main 030 031 private static Configuration configure() { 032 Configuration configuration = Db4o.newConfiguration(); 033 // add a custom typehandler support 034 035 TypeHandlerPredicate predicate = new TypeHandlerPredicate() { 036 public boolean match(ReflectClass classReflector, int version) { 037 GenericReflector reflector = new GenericReflector( 038 null, new JdkReflector(Thread.currentThread().getContextClassLoader())); 039 ReflectClass claxx = reflector.forName(StringBuffer.class.getName()); 040 boolean res = claxx.equals(classReflector); 041 return res; 042 } 043 }; 044 045 configuration.registerTypeHandler(predicate, new StringBufferHandler()); 046 return configuration; 047 } 048 // end configure 049 050 051 private static void testReadWriteDelete(){ 052 storeCar(); 053 // Does it still work after close? 054 retrieveCar(); 055 // Does deletion work? 056 deleteCar(); 057 retrieveCar(); 058 } 059 // end testReadWriteDelete 060 061 private static void retrieveCar() { 062 ObjectContainer container = database(configure()); 063 if (container != null){ 064 try { 065 ObjectSet result = container.query(Car.class); 066 Car car = null; 067 if (result.hasNext()){ 068 car = (Car)result.next(); 069 } 070 System.out.println("Retrieved: " + car); 071 } finally { 072 closeDatabase(); 073 } 074 } 075 } 076 // end retrieveCar 077 078 private static void deleteCar() { 079 ObjectContainer container = database(configure()); 080 if (container != null){ 081 try { 082 ObjectSet result = container.query(Car.class); 083 Car car = null; 084 if (result.hasNext()){ 085 car = (Car)result.next(); 086 } 087 container.delete(car); 088 System.out.println("Deleted: " + car); 089 } finally { 090 closeDatabase(); 091 } 092 } 093 } 094 // end deleteCar 095 096 private static void storeCar() { 097 new File(DB4O_FILE_NAME).delete(); 098 ObjectContainer container = database(configure()); 099 if (container != null){ 100 try { 101 Car car = new Car("BMW"); 102 container.store(car); 103 car = (Car)container.query(Car.class).next(); 104 System.out.println("Stored: " + car); 105 106 } finally { 107 closeDatabase(); 108 } 109 } 110 } 111 // end storeCar 112 113 private static void testCompare() { 114 new File(DB4O_FILE_NAME).delete(); 115 ObjectContainer container = database(configure()); 116 if (container != null){ 117 try { 118 Car car = new Car("BMW"); 119 container.store(car); 120 car = new Car("Ferrari"); 121 container.store(car); 122 car = new Car("Mercedes"); 123 container.store(car); 124 Query query = container.query(); 125 query.constrain(Car.class); 126 query.descend("model").orderAscending(); 127 ObjectSet result = query.execute(); 128 listResult(result); 129 130 } finally { 131 closeDatabase(); 132 } 133 } 134 } 135 // end testCompare 136 137 public static void testDefrag() throws IOException{ 138 new File(DB4O_FILE_NAME + ".backup").delete(); 139 storeCar(); 140 Defragment.defrag(DB4O_FILE_NAME); 141 retrieveCar(); 142 } 143 // end testDefrag 144 145 private static ObjectContainer database(Configuration configuration) { 146 if (_container == null) { 147 try { 148 _container = Db4o.openFile(configuration, DB4O_FILE_NAME); 149 } catch (DatabaseFileLockedException ex) { 150 System.out.println(ex.getMessage()); 151 } 152 } 153 return _container; 154 } 155 // end database 156 157 private static void closeDatabase() { 158 if (_container != null) { 159 _container.close(); 160 _container = null; 161 } 162 } 163 // end closeDatabase 164 165 166 private static void listResult(ObjectSet result) { 167 System.out.println(result.size()); 168 while(result.hasNext()) { 169 System.out.println(result.next()); 170 } 171 } 172 // end listResult 173 174}