Intro
 FAQ
 Features
 Limitations
 The basics

 XSD vs Dingo
 Class Diagram
 NUnit Tests
 Java code
 SourceForge

 Plugins
 Custom Builders
 Tutorial
 quicktips

 Samples
   conversion
   dashSchema
   contact
   foobar
   geneology

 Links
  NUnit

Plugin

Now that I've spent the last week bug hunting and fixing them, I can write a tutorial on how to write plugins for Dingo. This is one feature of Dingo, which Castor, JAXB, JAXME, and XSD do not have. Although I've used Castor, JAXB and XSD quite a bit over the last two of years, it isn't easy to add extension. Luckily, Castor provides java.beans support, so it covers most of the features I've needed. JAXB now provides the capability to extend an external class, but it still doesn't provide java.beans support. For the most part, Castor and JAXB provides 90% of the features I've needed to this point. XSD is another story altogether. I find it a poor excuse for a schema compiler. Once you add the pros/cons of XSD, I find it comes out negative. You're mileage with XSD might be different, if you only need to serialize simple objects to and from XML.

interfaceSample - directory structure

  • bin - contains the dll
  • config - a sample config file
  • xml - xsd
    • extended-po.xsd - sample schema which has all classes implement an interface
  • BaseClass.cs - sample base class
  • IHello.cs - simple interface with one method
  • PropertyChange.cs - java.beans style call back interface
  • SampleBuilder.cs - sample builder implementing DelegateInterface
Here's two examples of source generated by Dingo using po.xsd and extended-po.xsd.

po.xsd - basic schema without plugin
using System;
using System.Xml.Serialization;

namespace example.org
{
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://example.org")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.org",
      IsNullable=false)]
    [System.Xml.Serialization.XmlIncludeAttribute(typeof(USAddress))]
    public class PurchaseOrderType 
    {
        #region fields
        [System.Xml.Serialization.XmlElementAttribute("shipTo")]
        public USAddress shipTo;
        [System.Xml.Serialization.XmlElementAttribute("billTo")]
        public USAddress billTo;
        [System.Xml.Serialization.XmlElementAttribute("comment")]
        public string comment;
        [System.Xml.Serialization.XmlArrayItemAttribute("item")]
        public Items[] items;
        [System.Xml.Serialization.XmlAttributeAttribute()]
        public DateTime orderDate;
        #endregion

        public PurchaseOrderType()
        {
        }
        #region properties

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public USAddress ShipTo
        {
            get { return shipTo; }
            set { shipTo = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public USAddress BillTo
        {
            get { return billTo; }
            set { billTo = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public string Comment
        {
            get { return comment; }
            set { comment = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public Items[] Items
        {
            get { return items; }
            set { items = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public DateTime OrderDate
        {
            get { return orderDate; }
            set { orderDate = value; }
        }
        #endregion

        #region methods

        public USAddress getShipTo()
        {
            return shipTo;
        }

        public void setShipTo(USAddress param)
        {
            shipTo = param;
        }

        public USAddress getBillTo()
        {
            return billTo;
        }

        public void setBillTo(USAddress param)
        {
            billTo = param;
        }

        public string getComment()
        {
            return comment;
        }

        public void setComment(string param)
        {
            comment = param;
        }

        public Items[] getItems()
        {
            return items;
        }

        public void setItems(Items[] param)
        {
            items = param;
        }

        public DateTime getOrderDate()
        {
            return orderDate;
        }

        public void setOrderDate(DateTime param)
        {
            orderDate = param;
        }
        #endregion
    }
}
					

extended-po.xsd - modified version of po.xsd with plugin
using System;
using System.Xml.Serialization;
using woolfel.dingo.example;
using example.org;

namespace example.org.impl
{
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://example.org")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="http://example.org",
      IsNullable=false)]
    [System.Xml.Serialization.XmlIncludeAttribute(typeof(USAddress))]
    public class PurchaseOrderTypeImpl : PropertyChange, PurchaseOrderType
    {
        #region fields
        [System.Xml.Serialization.XmlElementAttribute("shipTo")]
        public USAddress shipTo;
        [System.Xml.Serialization.XmlElementAttribute("billTo")]
        public USAddress billTo;
        [System.Xml.Serialization.XmlElementAttribute("comment")]
        public string comment;
        public Items[] items;
        [System.Xml.Serialization.XmlElementAttribute("orderDate")]
        public DateTime orderDate;
        #endregion

        public PurchaseOrderTypeImpl()
        {
        }
        #region properties

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public USAddress ShipTo
        {
            get { return shipTo; }
            set { shipTo = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public USAddress BillTo
        {
            get { return billTo; }
            set { billTo = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public string Comment
        {
            get { return comment; }
            set { comment = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public Items[] Items
        {
            get { return items; }
            set { items = value; }
        }

        [System.Xml.Serialization.XmlIgnoreAttribute()]
        public DateTime OrderDate
        {
            get { return orderDate; }
            set { orderDate = value; }
        }
        #endregion

        #region methods

        public USAddress getShipTo()
        {
            return shipTo;
        }

        public void setShipTo(USAddress param)
        {
            if (shipTo != param ){
                USAddress old = shipTo;
                shipTo = param;
                // we would call a notify method of some kind to notify the listeners
            }
        }

        public USAddress getBillTo()
        {
            return billTo;
        }

        public void setBillTo(USAddress param)
        {
            if (billTo != param ){
                USAddress old = billTo;
                billTo = param;
                // we would call a notify method of some kind to notify the listeners
            }
        }

        public string getComment()
        {
            return comment;
        }

        public void setComment(string param)
        {
            if (comment != param ){
                string old = comment;
                comment = param;
                // we would call a notify method of some kind to notify the listeners
            }
        }

        public Items[] getItems()
        {
            return items;
        }

        public void setItems(Items[] param)
        {
            if (items != param ){
                Items[] old = items;
                items = param;
                // we would call a notify method of some kind to notify the listeners
            }
        }

        public DateTime getOrderDate()
        {
            return orderDate;
        }

        public void setOrderDate(DateTime param)
        {
            if (orderDate != param ){
                DateTime old = orderDate;
                orderDate = param;
                // we would call a notify method of some kind to notify the listeners
            }
        }
        #endregion
        #region interface_region
        protected System.Collections.ArrayList listener = new ArrayList();
        public void AddListener(object listener)
        {
            this.listener.Add(listener);
        }
        public void RemoveListener(object listener)
        {
            this.listener.Remove(listener);
        }
        #endregion
    }
}
					

There are couple of notable differences between the two versions. The first is the "set" methods in the extended version checks the value before changing it. In the sample plugin, the builder doesn't call a notify method as one would when implementing java.beans support. The second difference is the extended version contains some extra methods like AddListener and RemoveListener. These are methods one would implement for java.beans support.

The way Dingo handles delegation of code generation is the following.

  1. at startup, Dingo looks at the configuration settings
  2. the following configuration properties must be set to true for Dingo to delegate code generation
    • <add key="useInterfaces" value="true"/>
    • <add key="useJXBExtend" value="true"/>
  3. at least one of these properties must be set to true. The example has delegateMethods set to true
    • <add key="delegateFields" value="false"/>
    • <add key="delegateMethods" value="true"/>
    • <add key="delegateProps" value="false"/>
  4. register your assembly <add key="assembly" value="./interfaceSample.dll"/>
  5. register one or more interfaces or base classes: <add key="systemType" value="woolfel.dingo.example.PropertyChange"/>
  6. register your custom builder with the interface or base class: <add key="woolfel.dingo.example.PropertyChange" value="woolfel.dingo.example.SampleBuilder"/>
  7. when "useJXBExtend" is set to true, Dingo will set the delegation properties in the builder
  8. once all the settings are loaded, Dingo parses the schema
  9. the result produces an Array of TypeInfo classes
  10. Dingo then uses TypeBuilder to generate the source for each class
  11. TypeBuilder generates the source starting with the license text and down to the methods
  12. TypeBuilder will check to see if it should delegate the code generation for fields, properties and methods
  13. if it is set to true, dingo call the corresponding delegateXXX method
  14. the delegate method will attempt to lookup the registered builder using either the interface or base class as the key
  15. if one is found, it calls the corresponding method to generate the correct source
  16. the very last thing TypeBuilder does is call the method to generate additional code

Hopefully, that provides a good over view of how Dingo delegates at a high level. DelegatingBuilder is a fairly basic class. The TypeBuilder passes the entire TypeInfo object to the delegating builder. This way, each custom plugin can format all the methods as desired. If the TypeBuilder were to pass each MethodInfo object, it would be harder to make sure all methods are formatted uniformly.

using System;
using System.Text;
using woolfel.dingo.types;

namespace woolfel.dingo.builder
{
  /// <summary>
  /// Classes that generate specific method or property implementations need to implement
  /// this method and register it self.
  /// </summary>
  public interface DelegatingBuilder
  {
    /// <summary>
    /// Classes implementing the interface should provide a list of classes it supports
    /// to provide a simple way of discovering the classes it supports. This negates
    /// the need to manually enter all the mapping information in the config file. To
    /// turn off auto-config using introspection/reflection, set AutoLoad to false.
    /// </summary>
    /// <returns></returns>
    string[] GetClassList();
    /// <summary>
    /// Generate the fields required by the interface or base class
    /// </summary>
    /// <param name="type"></param>
    /// <param name="builder"></param>
    void GenerateField(TypeInfo type, StringBuilder builder);
    /// <summary>
    /// Generate the methods with the required calls. For example, a class that is
    /// used for GUI may have a call mechanism for notifying listeners it has changed.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="builder"></param>
    void GenerateMethod(TypeInfo type, StringBuilder builder);
    /// <summary>
    /// Generate the properties with the required calls. Properties are similar to
    /// methods. The main difference is properties cannot be defined in an interface
    /// and can only be defined in concrete classes. Properties are basically a short-
    /// hand syntax to reduce typing.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="builder"></param>
    void GenerateProperty(TypeInfo type, StringBuilder builder);
    /// <summary>
    /// Generate the required methods for the interface or base class. For example,
    /// an interface would define some methods a class has to implement. It is up
    /// to delegating builders to produce the source in this method.
    /// </summary>
    /// <param name="type"></param>
    /// <param name="builder"></param>
    void GenerateSupportingMethods(TypeInfo type, StringBuilder builder);
    void SetTabs(int count);
  }
}
					

One reason for having a method for formatting fields, properties and methods is so to provide greater flexibility. Even if a plugin provides implementation for all three methods, it is simple to turn off delegation with the config file. The first method in the interface "GetClassList" is a place holder for the second release. The idea is to have Dingo automatically scan all the classes in the assembly and register each builder without having to manually add them to the configuration. The last method in the interface is so TypeBuilder can set the number of tabs the delegating builder should use.

GenerateMethod - from SampleBuilder
    public void GenerateMethod(TypeInfo type, StringBuilder builder)
    {
      if(type.GetMethods().Count > 0)
      {
        if (!type.GetIsInterface())
        {
          WriteTabs(builder);
          builder.Append(Constants.METHODS_REGION + Constants.CRLF);
          // enumerate through the list of methods
          IEnumerator en = type.GetMethods().GetEnumerator();
          while(en.MoveNext())
          {
            MethodInfo mi = (MethodInfo)en.Current;
            if (mi.GetIsGet())
            {
              // we call the stock get method implemented by TypeBuilder
              TypeBuilder.FormatGetMethodItem(builder,mi);
            } 
            else 
            {
              // we call our custom method, which generates different code
              this.FormatMySetMethodItem(builder,mi);
            }
          }
          WriteTabs(builder);
          builder.Append(Constants.END_REGION + Constants.CRLF);
        } 
      } 
    }

    public void FormatMySetMethodItem(StringBuilder builder, MethodInfo meth)
    {
      // extra control carriage return
      builder.Append(Constants.CRLF);
      WriteTabs(builder);
      builder.Append(Constants.PUBLIC + " " + Constants.VOID + " ");
      builder.Append(meth.GetName());
      builder.Append(Constants.LEFT_PAREN + meth.GetFieldInfo().GetFieldType() +
        " ");
      builder.Append("param" + Constants.RIGHT_PAREN + Constants.CRLF);
      WriteTabs(builder);
      builder.Append(Constants.LEFT_CBRACE + Constants.CRLF);
      PushTab();
      // write out the set statement
      WriteTabs(builder);
      builder.Append("if (" + meth.GetFieldInfo().GetName() +
        " != param ){" + Constants.CRLF);
      PushTab();
      WriteTabs(builder);
      builder.Append(meth.GetFieldInfo().GetFieldType() + " old = " +
        meth.GetFieldInfo().GetName());
      builder.Append(Constants.SEMICOLON + Constants.CRLF);
      WriteTabs(builder);
      builder.Append(meth.GetFieldInfo().GetName() + " = param");
      builder.Append(Constants.SEMICOLON + Constants.CRLF);
      WriteTabs(builder);
      builder.Append("// we would call a notify method of some kind to notify
        the listeners" + Constants.CRLF);
      PopTab();
      WriteTabs(builder);
      builder.Append("}" + Constants.CRLF);
      PopTab();
      WriteTabs(builder);
      builder.Append(Constants.RIGHT_CBRACE + Constants.CRLF);
    }
					

The easiest way to write a custom plugin is to extend "woolfel.dingo.builder.AbstractBuilder" and implement "DelegatingBuilder". AbstractBuilder provides basic methods like PushTab, PopTab, and WriteTabs. It isn't necessary to extend AbstractBuilder, but it does save some work. I hope this tutorial provides a solid foundation for writing plugins.

Updated 9-10-2004 Peter

SourceForge.net Logo
Copyright 2004 Peter Lin