Having to write a lot of simple code once can be tedious and error prone – having to write it again for another language is even worse. Having to maintain both is just plain stupid. It would be quite handy to use a tool to generate all this basic code for us.
In my case I want the tool to create my standard code for my home automation software. I started with Java code but wanted to do an application in .NET and I didn’t want to port* or maintain both code bases – especially if the exact specification was still work-in-progress.
* Porting of the underlying base CDEF library was needed of course.
- plain old data objects
- fields can be
- basic types (int, long, string) or other objects
- lists of basic types or objects
- fields can be
- messages
- requests and responses
- fields can be
- basic types (int, long, string) or other objects
- lists of basic types or other objects
- encoding and decoding of data into byte buffers
- fields can be
- requests and responses
- languages
- Java
- .NET
The input to the tool is a form of specification defining all of the details and I chose to use XML to represent this data. Below is a very basic example of a simple class and message.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?xml version="1.0" encoding="utf-8"?> <specification company="abc" project="def" java-base-directory="${file-location}\java" net-base-directory="${file-location}\net" namespace="abc"> <classes> <class name="Error"> <attributes> <attribute name="number" type="int" /> <attribute name="code " type="int" /> <attribute name="message" type="string" /> </attributes> </class> <classes> <messages> <message name="Error" code="0x0000"> <description>Generic error response</description> <response> <parameter name="errors" type="Error" list="true" /> </response> </message> </messages> </specification> |
Running the tool and generating the code gives me the new source files. Note that the CdefMessage class is essentially a byte buffer. There is a constructor that pulls the data out of the message and an encode function to do the reverse.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
using System; using System.Collections.Generic; using hasoftware.Cdef; using abc.Api; namespace abc.Classes { public class Error { public int Number { get; set; } public int Code { get; set; } public string Message { get; set; } public Error() { } public Error(CdefMessage cdefMessage) { Number = cdefMessage.GetInt(); Code = cdefMessage.GetInt(); Message = cdefMessage.GetString(); } public void Encode(CdefMessage cdefMessage) { cdefMessage.PutInt(Number); cdefMessage.PutInt(Code); cdefMessage.PutString(Message); } } } |
In Java we get the same thing more or less. Ordering of encoding and decoding each field is important so we can send the objects between applications written in either language.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
package abc.api.classes; import hasoftware.cdef.CDEFMessage; import java.util.Collection; import java.util.LinkedList; public class Error { private int _number; private int _code; private String _message; public Error() { } public Error(CDEFMessage cdefMessage) { _number = cdefMessage.getInt(); _code = cdefMessage.getInt(); _message = cdefMessage.getString(); } public void encode(CDEFMessage cdefMessage) { cdefMessage.putInt(_number); cdefMessage.putInt(_code); cdefMessage.putString(_message); } public int getNumber() { return _number; } public void setNumber(int number) { _number = number; } public int getCode() { return _code; } public void setCode(int code) { _code = code; } public String getMessage() { return _message; } public void setMessage(String message) { _message = message; } } |
Here is the .NET Error message code, the Java is much the same. You can see how I handle lists of data objects and how they get encoded and decoded as required.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System; using System.Collections.Generic; using abc.Api; using abc.Classes; using hasoftware.Cdef; namespace abc.Messages { public class ErrorResponse : Message { public List<Error> Errors { get; set; } public ErrorResponse(int transactionNumber) : base(Api.FunctionCode.Error, transactionNumber, CdefSystemFlags.Response) { Errors = new List<Error>(); } public ErrorResponse(CdefMessage cdefMessage) : base(cdefMessage) { { Errors = new List<Error>(); int c = cdefMessage.GetInt(); for (int i=0; i<c; i++) { Errors.Add(new Error(cdefMessage)); } } } public override void Encode(CdefMessage cdefMessage) { base.Encode(cdefMessage); cdefMessage.PutInt(Errors.Count); foreach (var obj in Errors) { obj.Encode(cdefMessage); } } } } |
There are a few other helper classes that get created to make everything work but already we can create all this basic code with very little effort, no mistakes and knowing that both language libraries are in sync. This suits me as I’m very lazy and the less I have think about the better.