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:

StringBuilderHandler.cs: Write
1public void Write(IWriteContext context, object obj) 2 { 3 string str = ((StringBuilder)obj).ToString(); 4 IWriteBuffer buffer = context; 5 buffer.WriteInt(str.Length); 6 WriteToBuffer(buffer, str); 7 }
StringBuilderHandler.cs: WriteToBuffer
01private static void WriteToBuffer(IWriteBuffer buffer, string str) 02 { 03 int length = str.Length; 04 char[] chars = new char[length]; 05 str.CopyTo(0, chars, 0, length); 06 for (int i = 0; i < length; i++) 07 { 08 buffer.WriteByte((byte)(chars[i] & 0xff)); 09 buffer.WriteByte((byte)(chars[i] >> 8)); 10 } 11 }
StringBuilderHandler.vb: Write
1Public Sub Write(ByVal context As IWriteContext, ByVal obj As Object) Implements ITypeHandler4.Write 2 Dim str As String = DirectCast(obj, StringBuilder).ToString() 3 Dim buffer As IWriteBuffer = context 4 buffer.WriteInt(str.Length) 5 WriteToBuffer(buffer, str) 6 End Sub
StringBuilderHandler.vb: WriteToBuffer
1Private Shared Sub WriteToBuffer(ByVal buffer As IWriteBuffer, ByVal str As String) 2 Dim length As Integer = str.Length 3 Dim chars As Char() = New Char(length - 1) {} 4 str.CopyTo(0, chars, 0, length) 5 For i As Integer = 0 To length - 1 6 buffer.WriteByte(CByte(AscW(chars(i)) And 255)) 7 buffer.WriteByte(CByte(AscW(chars(i)) >> 8)) 8 Next 9 End Sub

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:

StringBuilderHandler.cs: Read
01public object Read(IReadContext context) 02 { 03 IReadBuffer buffer = context; 04 string str = ""; 05 int length = buffer.ReadInt(); 06 if (length > 0) 07 { 08 str = ReadBuffer(buffer, length); 09 } 10 return new StringBuilder(str); 11 }
StringBuilderHandler.cs: ReadBuffer
1private static string ReadBuffer(IReadBuffer buffer, int length) 2 { 3 char[] chars = new char[length]; 4 for (int ii = 0; ii < length; ii++) 5 { 6 chars[ii] = (char)((buffer.ReadByte() & 0xff) | ((buffer.ReadByte() & 0xff) << 8)); 7 } 8 return new string(chars, 0, length); 9 }
StringBuilderHandler.vb: Read
1Public Function Read(ByVal context As IReadContext) As Object Implements ITypeHandler4.Read 2 Dim buffer As IReadBuffer = context 3 Dim str As String = "" 4 Dim length As Integer = buffer.ReadInt() 5 If length > 0 Then 6 str = ReadBuffer(buffer, length) 7 End If 8 Return New StringBuilder(str) 9 End Function
StringBuilderHandler.vb: ReadBuffer
1Private Shared Function ReadBuffer(ByVal buffer As IReadBuffer, ByVal length As Integer) As String 2 Dim chars As Char() = New Char(length - 1) {} 3 For ii As Integer = 0 To length - 1 4 chars(ii) = ChrW(((buffer.ReadByte() And 255) Or ((buffer.ReadByte() And 255) << 8))) 5 Next 6 Return New String(chars, 0, length) 7 End Function

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

StringBuilderHandler.cs: Delete
1public void Delete(IDeleteContext context) 2 { 3 context.ReadSlot(); 4 }
StringBuilderHandler.vb: Delete
1Public Sub Delete(ByVal context As IDeleteContext) Implements ITypeHandler4.Delete 2 context.ReadSlot() 3 End Sub

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):

StringBuilderHandler.cs: PrepareComparison
1public IPreparedComparison PrepareComparison(IContext con, object obj) 2 { 3 return new PreparedComparison(obj); 4 }
StringBuilderHandler.cs: PreparedComparison
01private class PreparedComparison : IPreparedComparison 02 { 03 object _source = null; 04 05 public PreparedComparison(object source) 06 { 07 _source = source; 08 } 09 10 public int CompareTo(object target) 11 { 12 return Compare((StringBuilder)_source, (StringBuilder)target); 13 } 14 }

StringBuilderHandler.cs: Compare
01private static int Compare(StringBuilder a_compare, StringBuilder a_with) 02 { 03 if (a_compare == null) 04 { 05 if (a_with == null) 06 { 07 return 0; 08 } 09 return -1; 10 } 11 if (a_with == null) 12 { 13 return 1; 14 } 15 char[] c_compare = new char[a_compare.Length]; 16 a_compare.CopyTo(0, c_compare, 0, a_compare.Length); 17 char[] c_with = new char[a_with.Length]; 18 a_with.CopyTo(0, c_with, 0, a_with.Length); 19 20 return CompareChars(c_compare, c_with); 21 }
StringBuilderHandler.cs: CompareChars
01private static int CompareChars(char[] compare, char[] with) 02 { 03 int min = compare.Length < with.Length ? compare.Length : with.Length; 04 for (int i = 0; i < min; i++) 05 { 06 if (compare[i] != with[i]) 07 { 08 return compare[i] - with[i]; 09 } 10 } 11 return compare.Length - with.Length; 12 }
StringBuilderHandler.vb: PrepareComparison
1Public Function PrepareComparison(ByVal con As IContext, ByVal obj As Object) As IPreparedComparison Implements ITypeHandler4.PrepareComparison 2 Return New PreparedComparison(obj) 3 End Function
StringBuilderHandler.vb: PreparedComparison
01Private Class PreparedComparison 02 Implements IPreparedComparison 03 Private _source As Object = Nothing 04 05 Public Sub New(ByVal source As Object) 06 _source = source 07 End Sub 08 09 Public Function CompareTo(ByVal target As Object) As Integer Implements IPreparedComparison.CompareTo 10 Return Compare(DirectCast(_source, StringBuilder), DirectCast(target, StringBuilder)) 11 End Function 12 End Class

StringBuilderHandler.vb: Compare
01Private Shared Function Compare(ByVal a_compare As StringBuilder, ByVal a_with As StringBuilder) As Integer 02 If a_compare Is Nothing Then 03 If a_with Is Nothing Then 04 Return 0 05 End If 06 Return -1 07 End If 08 If a_with Is Nothing Then 09 Return 1 10 End If 11 Dim c_compare As Char() = New Char(a_compare.Length - 1) {} 12 a_compare.CopyTo(0, c_compare, 0, a_compare.Length) 13 Dim c_with As Char() = New Char(a_with.Length - 1) {} 14 a_with.CopyTo(0, c_with, 0, a_with.Length) 15 16 Return CompareChars(c_compare, c_with) 17 End Function
StringBuilderHandler.vb: CompareChars
1Private Shared Function CompareChars(ByVal compare As Char(), ByVal [with] As Char()) As Integer 2 Dim min As Integer = IIf(compare.Length < [with].Length, compare.Length, [with].Length) 3 For i As Integer = 0 To min - 1 4 If compare(i) <> [with](i) Then 5 Return compare(i).CompareTo([with](i)) 6 End If 7 Next 8 Return compare.Length - [with].Length 9 End Function

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):

StringBuilderHandler.cs: Defragment
1public void Defragment(IDefragmentContext context) 2 { 3 // To stay compatible with the old marshaller family 4 // In the marshaller family 0 number 8 represented 5 // length reqiored to store ID and object length information 6 context.IncrementOffset(8); 7 }

StringBuilderHandler.vb: Defragment
1Public Sub Defragment(ByVal context As IDefragmentContext) Implements ITypeHandler4.Defragment 2 ' To stay compatible with the old marshaller family 3 ' In the marshaller family 0 number 4 represented 4 ' length reqiored to store ID and object length information 5 context.IncrementOffset(4) 6 End Sub

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.cs
001/* Copyright (C) 2004 - 2008 db4objects Inc. http://www.db4o.com */ 002using System.Text; 003using System.IO; 004 005using Db4objects.Db4o; 006using Db4objects.Db4o.Config; 007using Db4objects.Db4o.Defragment; 008using Db4objects.Db4o.Ext; 009using Db4objects.Db4o.Query; 010using Db4objects.Db4o.Reflect; 011using Db4objects.Db4o.Reflect.Net; 012using Db4objects.Db4o.Reflect.Generic; 013using Db4objects.Db4o.Typehandlers; 014 015namespace Db4objects.Db4odoc.Typehandler 016{ 017 018 public class TypehandlerExample 019 { 020 021 private readonly static string Db4oFileName = "reference.db4o"; 022 private static IObjectContainer _container = null; 023 024 025 public static void Main(string[] args) 026 { 027 TestReadWriteDelete(); 028 //TestDefrag(); 029 TestCompare(); 030 } 031 // end Main 032 033 private class TypeHandlerPredicate : ITypeHandlerPredicate 034 { 035 public bool Match(IReflectClass classReflector, int version) 036 { 037 IReflector reflector = classReflector.Reflector(); 038 IReflectClass claxx = reflector.ForClass(typeof(StringBuilder)); 039 bool res = claxx.Equals(classReflector); 040 return res; 041 042 } 043 } 044 // end TypeHandlerPredicate 045 046 private static IConfiguration Configure() 047 { 048 IConfiguration configuration = Db4oFactory.NewConfiguration(); 049 // add a custom typehandler support 050 051 configuration.RegisterTypeHandler(new TypeHandlerPredicate(), 052 new StringBuilderHandler()); 053 return configuration; 054 } 055 // end Configure 056 057 058 private static void TestReadWriteDelete() 059 { 060 StoreCar(); 061 // Does it still work after close? 062 RetrieveCar(); 063 // Does deletion work? 064 DeleteCar(); 065 RetrieveCar(); 066 } 067 // end TestReadWriteDelete 068 069 private static void RetrieveCar() 070 { 071 IObjectContainer container = Database(Configure()); 072 if (container != null) 073 { 074 try 075 { 076 IObjectSet result = container.Query(typeof(Car)); 077 Car car = null; 078 if (result.HasNext()) 079 { 080 car = (Car)result.Next(); 081 } 082 System.Console.WriteLine("Retrieved: " + car); 083 } 084 finally 085 { 086 CloseDatabase(); 087 } 088 } 089 } 090 // end RetrieveCar 091 092 private static void DeleteCar() 093 { 094 IObjectContainer container = Database(Configure()); 095 if (container != null) 096 { 097 try 098 { 099 IObjectSet result = container.Query(typeof(Car)); 100 Car car = null; 101 if (result.HasNext()) 102 { 103 car = (Car)result.Next(); 104 } 105 container.Delete(car); 106 System.Console.WriteLine("Deleted: " + car); 107 } 108 finally 109 { 110 CloseDatabase(); 111 } 112 } 113 } 114 // end DeleteCar 115 116 private static void StoreCar() 117 { 118 File.Delete(Db4oFileName); 119 IObjectContainer container = Database(Configure()); 120 if (container != null) 121 { 122 try 123 { 124 Car car = new Car("BMW"); 125 container.Store(car); 126 car = (Car)container.Query(typeof(Car)).Next(); 127 System.Console.WriteLine("Stored: " + car); 128 129 } 130 finally 131 { 132 CloseDatabase(); 133 } 134 } 135 } 136 // end StoreCar 137 138 private static void TestCompare() 139 { 140 File.Delete(Db4oFileName); 141 IObjectContainer container = Database(Configure()); 142 if (container != null) 143 { 144 try 145 { 146 Car car = new Car("BMW"); 147 container.Store(car); 148 car = new Car("Ferrari"); 149 container.Store(car); 150 car = new Car("Mercedes"); 151 container.Store(car); 152 IQuery query = container.Query(); 153 query.Constrain(typeof(Car)); 154 query.Descend("model").OrderAscending(); 155 IObjectSet result = query.Execute(); 156 ListResult(result); 157 158 } 159 finally 160 { 161 CloseDatabase(); 162 } 163 } 164 } 165 // end TestCompare 166 167 private static void TestDefrag() 168 { 169 File.Delete(Db4oFileName + ".backup"); 170 StoreCar(); 171 Defragment.Defrag(Db4oFileName); 172 RetrieveCar(); 173 } 174 // end TestDefrag 175 176 private static IObjectContainer Database(IConfiguration configuration) 177 { 178 if (_container == null) 179 { 180 try 181 { 182 _container = Db4oFactory.OpenFile(configuration, Db4oFileName); 183 } 184 catch (DatabaseFileLockedException ex) 185 { 186 System.Console.WriteLine(ex.Message); 187 } 188 } 189 return _container; 190 } 191 // end Database 192 193 private static void CloseDatabase() 194 { 195 if (_container != null) 196 { 197 _container.Close(); 198 _container = null; 199 } 200 } 201 // end CloseDatabase 202 203 204 private static void ListResult(IObjectSet result) 205 { 206 System.Console.WriteLine(result.Size()); 207 while (result.HasNext()) 208 { 209 System.Console.WriteLine(result.Next()); 210 } 211 } 212 // end ListResult 213 214 } 215}

TypehandlerExample.vb
001' Copyright (C) 2004 - 2008 db4objects Inc. http://www.db4o.com 002 003Imports System.Text 004Imports System.IO 005 006Imports Db4objects.Db4o 007Imports Db4objects.Db4o.Config 008Imports Db4objects.Db4o.Defragment 009Imports Db4objects.Db4o.Ext 010Imports Db4objects.Db4o.Query 011Imports Db4objects.Db4o.Reflect 012Imports Db4objects.Db4o.Reflect.Net 013Imports Db4objects.Db4o.Reflect.Generic 014Imports Db4objects.Db4o.Typehandlers 015 016Namespace Db4objects.Db4odoc.Typehandler 017 018 Public Class TypehandlerExample 019 020 Private Shared ReadOnly Db4oFileName As String = "reference.db4o" 021 Private Shared _container As IObjectContainer = Nothing 022 023 024 Public Shared Sub Main(ByVal args As String()) 025 TestReadWriteDelete() 026 'TestDefrag() 027 TestCompare() 028 End Sub 029 ' end Main 030 031 Private Class TypeHandlerPredicate 032 Implements ITypeHandlerPredicate 033 034 Public Function Match(ByVal classReflector As IReflectClass, ByVal version As Integer) As Boolean Implements ITypeHandlerPredicate.Match 035 Dim reflector As IReflector = classReflector.Reflector() 036 Dim claxx As IReflectClass = reflector.ForClass(GetType(StringBuilder)) 037 Dim res As Boolean = claxx Is classReflector 038 Return res 039 040 End Function 041 042 End Class 043 ' end TypeHandlerPredicate 044 045 Private Shared Function Configure() As IConfiguration 046 Dim configuration As IConfiguration = Db4oFactory.NewConfiguration() 047 ' add a custom typehandler support 048 049 configuration.RegisterTypeHandler(New TypeHandlerPredicate(), New StringBuilderHandler()) 050 Return configuration 051 End Function 052 ' end Configure 053 054 055 Private Shared Sub TestReadWriteDelete() 056 StoreCar() 057 ' Does it still work after close? 058 RetrieveCar() 059 ' Does deletion work? 060 DeleteCar() 061 RetrieveCar() 062 End Sub 063 ' end TestReadWriteDelete 064 065 Private Shared Sub RetrieveCar() 066 Dim container As IObjectContainer = Database(Configure()) 067 If container IsNot Nothing Then 068 Try 069 Dim query As IQuery = container.Query() 070 query.Constrain(GetType(Car)) 071 Dim result As IObjectSet = query.Execute() 072 Dim car As Car = Nothing 073 If result.HasNext() Then 074 car = DirectCast(result.[Next](), Car) 075 End If 076 If car Is Nothing Then 077 System.Console.WriteLine("Retrieved: Nothing") 078 Else 079 System.Console.WriteLine("Retrieved: " + car.ToString()) 080 End If 081 082 Finally 083 CloseDatabase() 084 End Try 085 End If 086 End Sub 087 ' end RetrieveCar 088 089 Private Shared Sub DeleteCar() 090 Dim container As IObjectContainer = Database(Configure()) 091 If container IsNot Nothing Then 092 Try 093 Dim result As IObjectSet = container.Query(GetType(Car)) 094 Dim car As Car = Nothing 095 If result.HasNext() Then 096 car = DirectCast(result.[Next](), Car) 097 End If 098 container.Delete(car) 099 System.Console.WriteLine("Deleted: " + car.ToString()) 100 Finally 101 CloseDatabase() 102 End Try 103 End If 104 End Sub 105 ' end DeleteCar 106 107 Private Shared Sub StoreCar() 108 File.Delete(Db4oFileName) 109 Dim container As IObjectContainer = Database(Configure()) 110 If container IsNot Nothing Then 111 Try 112 Dim car As New Car("BMW") 113 container.Store(car) 114 car = DirectCast(container.Query(GetType(Car)).[Next](), Car) 115 116 System.Console.WriteLine("Stored: " + car.ToString()) 117 Finally 118 CloseDatabase() 119 End Try 120 End If 121 End Sub 122 ' end StoreCar 123 124 Private Shared Sub TestCompare() 125 File.Delete(Db4oFileName) 126 Dim container As IObjectContainer = Database(Configure()) 127 If container IsNot Nothing Then 128 Try 129 Dim car As New Car("BMW") 130 container.Store(car) 131 car = New Car("Ferrari") 132 container.Store(car) 133 car = New Car("Mercedes") 134 container.Store(car) 135 Dim query As IQuery = container.Query() 136 query.Constrain(GetType(Car)) 137 query.Descend("model").OrderAscending() 138 Dim result As IObjectSet = query.Execute() 139 140 ListResult(result) 141 Finally 142 CloseDatabase() 143 End Try 144 End If 145 End Sub 146 ' end TestCompare 147 148 Private Shared Sub TestDefrag() 149 File.Delete(Db4oFileName + ".backup") 150 StoreCar() 151 Defragment.Defrag(Db4oFileName) 152 RetrieveCar() 153 End Sub 154 ' end TestDefrag 155 156 Private Shared Function Database(ByVal configuration As IConfiguration) As IObjectContainer 157 If _container Is Nothing Then 158 Try 159 _container = Db4oFactory.OpenFile(configuration, Db4oFileName) 160 Catch ex As DatabaseFileLockedException 161 System.Console.WriteLine(ex.Message) 162 End Try 163 End If 164 Return _container 165 End Function 166 ' end Database 167 168 Private Shared Sub CloseDatabase() 169 If _container IsNot Nothing Then 170 _container.Close() 171 _container = Nothing 172 End If 173 End Sub 174 ' end CloseDatabase 175 176 177 Private Shared Sub ListResult(ByVal result As IObjectSet) 178 System.Console.WriteLine(result.Size()) 179 While result.HasNext() 180 System.Console.WriteLine(result.[Next]()) 181 End While 182 End Sub 183 ' end ListResult 184 185 End Class 186End Namespace