12. Translators

In the last chapter we have covered the alternative configurations db4o offers for object reinstantiation. What's left to see is how we can store objects of a class that can't be cleanly stored with either of these approaches.

    12.1. An example class

    For this example we'll be using a hypothetical LocalizedItemList class which binds together culture information with a list of items.
    System.Globalization.CultureInfo is particularly interesting because it internally holds a native pointer to a system structure which in turn cannot be cleanly stored by db4o.
    namespace com.db4o.f1.chapter6
    {
        using System.Globalization;
        /// <summary>
        /// A CultureInfo aware list of objects.
        /// CultureInfo objects hold a native pointer to
        /// a system structure.
        /// </summary>
        public class LocalizedItemList
        {
            CultureInfo _culture;
            string[] _items;
            public LocalizedItemList(CultureInfo culture, string[] items)
            {
                _culture = culture;
                _items = items;
            }
            override public string ToString()
            {
                return string.Join(_culture.TextInfo.ListSeparator + " ", _items);
            }
        }
    }

    We'll be using this code to store and retrieve and instance of this class with different configuration settings:
    public static void tryStoreAndRetrieve()
        {
            ObjectContainer db = Db4o.openFile(Util.YapFileName);
            try
            {
                string[] champs = new string[] { "Ayrton Senna", "Nelson Piquet" };
                LocalizedItemList LocalizedItemList = new LocalizedItemList(CultureInfo.CreateSpecificCulture("pt-BR"), champs);
                System.Console.WriteLine("ORIGINAL: {0}", LocalizedItemList);
                db.set(LocalizedItemList);
            }
            catch (Exception x)
            {
                System.Console.WriteLine(x);
                return;
            }
            finally
            {
                db.close();
            }
            db = Db4o.openFile(Util.YapFileName);
            try
            {
                ObjectSet result = db.get(typeof(LocalizedItemList));
                while (result.hasNext())
                {
                    LocalizedItemList LocalizedItemList = (LocalizedItemList)result.next();
                    System.Console.WriteLine("RETRIEVED: {0}", LocalizedItemList);
                    db.delete(LocalizedItemList);
                }
            }
            finally
            {
                db.close();
            }
        }

    Let's verify that both approaches to object reinstantiation will fail for this class.

      12.1.1. Using the constructor

      [tryStoreWithCallConstructors]
      Db4o.configure().exceptionsOnNotStorable(true);
          Db4o.configure().objectClass(typeof(CultureInfo))
              .callConstructor(true);
          tryStoreAndRetrieve();

      At storage time, db4o tests the only available constructor with null arguments and runs into a NullPointerException, so it refuses to accept our object.
      (Note that this test only occurs when configured with exceptionsOnNotStorable - otherwise db4o will silently fail when trying to reinstantiate the object.)

      12.1.2. Bypassing the constructor

      [tryStoreWithoutCallConstructors]
      Db4o.configure().objectClass(typeof(CultureInfo))
              .callConstructor(false);
          // trying to store objects that hold onto
          // system resources can be pretty nasty
          // uncomment the following line to see
          // how nasty it can be
          //tryStoreAndRetrieve();

      This still does not work for our case because the native pointer will definetely be invalid. In fact this example crashes the Common Language Runtime.

    12.2. The Translator API

    So how do we get our object into the database, now that everything seems to fail? Db4o provides a way to specify a custom way of storing and retrieving objects through the ObjectTranslator and ObjectConstructor interfaces.

      12.2.1. ObjectTranslator

      The ObjectTranslator API looks like this:
      public Object onStore(ObjectContainer container,
                            Object applicationObject);
      public void onActivate(ObjectContainer container,
                             Object applicationObject,
                             Object storedObject);
      public Class storedClass ();

      The usage is quite simple: When a translator is configured for a class, db4o will call its onStore method with a reference to the database and the instance to be stored as a parameter and will store the object returned. This object's type has to be primitive from a db4o point of view and it has to match the type specification returned by storedClass().
      On retrieval, db4o will create a blank object of the target class (using the configured instantiation method) and then pass it on to onActivate() along with the stored object to be set up accordingly.

      12.2.2. ObjectConstructor

      However, this will only work if the application object's class provides some way to recreate its state from the information contained in the stored object, which is not the case for CultureInfo.
      For these cases db4o provides an extension to the ObjectTranslator interface, ObjectConstructor, which declares one additional method:
      public Object onInstantiate(ObjectContainer container,
                                  Object storedObject);

      If db4o detects a configured translator to be an ObjectConstructor implementation, it will pass the stored class instance to the onInstantiate() method and use the result as a blank application object to be processed by onActivate().
      Note that, while in general configured translators are applied to subclasses, too, ObjectConstructor application object instantiation will not be used for subclasses (which wouldn't make much sense, anyway), so ObjectConstructors have to be configured for the concrete classes.

    12.3. A translator implementation

    To translate CultureInfo instances, we will store only their name since this is enough to recreate them later. Note that we don't have to do any work in onActivate(), since object reinstantiation is already fully completed in onInstantiate().
    namespace com.db4o.f1.chapter6
    {
        using System.Globalization;
        using com.db4o;
        using com.db4o.config;
        
        public class CultureInfoTranslator : ObjectConstructor
        {
            public object onStore(ObjectContainer container, object applicationObject)
            {
                System.Console.WriteLine("onStore for {0}", applicationObject);
                return ((CultureInfo)applicationObject).Name;
            }
            
            public object onInstantiate(ObjectContainer container, object storedObject)
            {
                System.Console.WriteLine("onInstantiate for {0}", storedObject);
                string name = (string)storedObject;
                return CultureInfo.CreateSpecificCulture(name);
            }
            
            public void onActivate(ObjectContainer container, object applicationObject, object storedObject)
            {
                System.Console.WriteLine("onActivate for {0}/{1}", applicationObject, storedObject);
            }
            
            public j4o.lang.Class storedClass()
            {
                return j4o.lang.Class.getClassForType(typeof(string));
            }
        }
    }

    Let's try it out:
    [storeWithTranslator]
    Db4o.configure().objectClass(typeof(CultureInfo))
            .translate(new CultureInfoTranslator());
        tryStoreAndRetrieve();
        Db4o.configure().objectClass(typeof(CultureInfo))
            .translate(null);
    OUTPUT:
    ORIGINAL: 42/Test: 4
    onStore for 42/Test: 4
    onInstantiate for [Ljava.lang.Object;@b360860
    onActivate for 42/Test: 4 / [Ljava.lang.Object;@10463704
    RETRIEVED: 42/Test: 4


12.4. Conclusion

For classes that cannot cleanly be stored and retrieved with db4o's standard object instantiation mechanisms, db4o provides an API to specify custom reinstantiation strategies. These also come in two flavors: ObjectTranslators let you reconfigure the state of a 'blank' application object reinstantiated by db4o, ObjectConstructors also take care of instantiating the application object itself.

12.5. Full source

namespace com.db4o.f1.chapter6
{
    using System;
    using System.Globalization;
    using com.db4o;
    using com.db4o.f1;
    
    public class TranslatorExample : Util
    {
        public static void Main(string[] args)
        {
            tryStoreWithCallConstructors();
            tryStoreWithoutCallConstructors();
            storeWithTranslator();
        }
        
        public static void tryStoreWithCallConstructors()
        {
            Db4o.configure().exceptionsOnNotStorable(true);
            Db4o.configure().objectClass(typeof(CultureInfo))
                .callConstructor(true);
            tryStoreAndRetrieve();
        }
        
        public static void tryStoreWithoutCallConstructors()
        {
            Db4o.configure().objectClass(typeof(CultureInfo))
                .callConstructor(false);
            // trying to store objects that hold onto
            // system resources can be pretty nasty
            // uncomment the following line to see
            // how nasty it can be
            //tryStoreAndRetrieve();
        }
        
        public static void storeWithTranslator()
        {
            Db4o.configure().objectClass(typeof(CultureInfo))
                .translate(new CultureInfoTranslator());
            tryStoreAndRetrieve();
            Db4o.configure().objectClass(typeof(CultureInfo))
                .translate(null);
        }
        
        public static void tryStoreAndRetrieve()
        {
            ObjectContainer db = Db4o.openFile(Util.YapFileName);
            try
            {
                string[] champs = new string[] { "Ayrton Senna", "Nelson Piquet" };
                LocalizedItemList LocalizedItemList = new LocalizedItemList(CultureInfo.CreateSpecificCulture("pt-BR"), champs);
                System.Console.WriteLine("ORIGINAL: {0}", LocalizedItemList);
                db.set(LocalizedItemList);
            }
            catch (Exception x)
            {
                System.Console.WriteLine(x);
                return;
            }
            finally
            {
                db.close();
            }
            db = Db4o.openFile(Util.YapFileName);
            try
            {
                ObjectSet result = db.get(typeof(LocalizedItemList));
                while (result.hasNext())
                {
                    LocalizedItemList LocalizedItemList = (LocalizedItemList)result.next();
                    System.Console.WriteLine("RETRIEVED: {0}", LocalizedItemList);
                    db.delete(LocalizedItemList);
                }
            }
            finally
            {
                db.close();
            }
        }
    }
}



--
generated by
Doctor courtesy of db4objects Inc.