Atlas Porting Guide

From WorldForgeWiki
Jump to: navigation, search

Porting Process

  • Handshake: Implement the atlas handshake according to: Atlas Handshake
  • Codec(s): Many codec choices (XML,Bach,Packed) => Start by implement only Packed codec according to Atlas Codecs).
  • Decide whether you want to use generic object model (map, list hierarchy) or generated object model. If you want to use generated object model modify codec to manipulate directly your own Atlas value objects or write another conversion layer between generic codec object model and generated Atlas value object model. You can generate source class files from: [1]
  • Conversation(s): Many conversations => Start by implementing latest version of core conversations according to Atlas Protocol.

If all of this sounds like technical jargon consider using one of the existing Atlas implementations.

C# Packed Codec Decoder

using System;
using System.Collections.Generic;
using System.Text;

namespace MembraneCore.Network.Connection.Codec.PackedCodec
{
    public class PackedCodecDecoder
    {
        private const int STATE_STACK_MAX_DEPTH = 100;

        private enum ParserOperation
        {
            ParsingMap,
            ParsingList,
            ParsingString,
            ParsingFloat,
            ParsingInt,
            ParsingName,
            ParsingEncodedCharacter,
            None
        }

        private class ParserState
        {
            public ParserState()
            {
                this.name = "";
                this.encodedValue = new StringBuilder("");
                this.operation = ParserOperation.None;
            }

            public ParserOperation operation;
            public string name;
            public StringBuilder encodedValue;
            public object value;

            public override string ToString()
            {
                return "<" + operation.ToString() + ":" + name + ":" + encodedValue + ">";
            }
        }

        private ParserState[] stateStack = new ParserState[STATE_STACK_MAX_DEPTH];
        private int stateStackIndex = -1;

        public PackedCodecDecoder()
        {
            for (int i = 0; i < stateStack.Length; i++)
            {
                stateStack[i] = new ParserState();
            }
        }

        public object Decode(char ch)
        {
            string completedPrimitiveName = null;
            object completedPrimitive = null;

            if (stateStackIndex > -1)
            {
                if (stateStack[stateStackIndex].operation == ParserOperation.ParsingEncodedCharacter
                    && stateStack[stateStackIndex].encodedValue.Length == 1)
                {
                    // end of encoded character parsing
                    // this has to be handled first because its ending is dependent of number of 
                    // decoded characters instead of special characters
                    byte[] value = new byte[1];
                    value[0] = HexCharToByte(stateStack[stateStackIndex].encodedValue[0]);
                    value[0] *= 16;
                    value[0] += HexCharToByte(ch);
                    popStack();
                    stateStack[stateStackIndex].encodedValue.Append(Encoding.ASCII.GetChars(value)[0]);
                    return null;
                }

                if ((stateStack[stateStackIndex].operation == ParserOperation.ParsingString ||
                     stateStack[stateStackIndex].operation == ParserOperation.ParsingInt ||
                     stateStack[stateStackIndex].operation == ParserOperation.ParsingFloat ||
                     stateStack[stateStackIndex].operation == ParserOperation.ParsingEncodedCharacter ||
                     stateStack[stateStackIndex].operation == ParserOperation.ParsingName
                    ) && (ch != '+' && ch != '[' && ch != ']' && ch != '(' && ch != ')' && ch != '@' && ch != '#' && ch != '$' && ch != '=' && ch != '\n' && ch != '\r'))
                {
                    // adding character to encoded value of the current parser state
                    stateStack[stateStackIndex].encodedValue.Append(ch);
                    return null;
                }

                if (stateStack[stateStackIndex].operation == ParserOperation.ParsingName)
                {
                    // end of name
                    if (ch == '=')
                    {
                        stateStack[stateStackIndex-1].name = stateStack[stateStackIndex].encodedValue.ToString();
                        popStack();
                        return null;
                    }
                }

                if ((stateStack[stateStackIndex].operation == ParserOperation.ParsingString ||
                     stateStack[stateStackIndex].operation == ParserOperation.ParsingFloat ||
                     stateStack[stateStackIndex].operation == ParserOperation.ParsingInt
                    ) && (ch == ']' || ch == ')'))
                {
                    // last primitive value inside list (collections have explicit closing character)
                    string lastContainedPrimitiveName = null;
                    object lastContainedPrimitive = null;
                    switch (stateStack[stateStackIndex].operation)
                    {
                        case ParserOperation.ParsingString:
                            lastContainedPrimitiveName = stateStack[stateStackIndex].name;
                            lastContainedPrimitive = stateStack[stateStackIndex].encodedValue.ToString();
                            popStack();
                            break;
                        case ParserOperation.ParsingFloat:
                            lastContainedPrimitiveName = stateStack[stateStackIndex].name;
                            lastContainedPrimitive = Convert.ToDouble(stateStack[stateStackIndex].encodedValue.ToString().Replace('.', ','));
                            popStack();
                            break;
                        case ParserOperation.ParsingInt:
                            lastContainedPrimitiveName = stateStack[stateStackIndex].name;
                            lastContainedPrimitive = Convert.ToInt32(stateStack[stateStackIndex].encodedValue.ToString()); ;
                            popStack();
                            break;
                    }
                    if (stateStack[stateStackIndex].operation == ParserOperation.ParsingMap)
                    {
                        ((Dictionary<string, object>)stateStack[stateStackIndex].value)[lastContainedPrimitiveName] = lastContainedPrimitive;
                    }
                    else if (stateStack[stateStackIndex].operation == ParserOperation.ParsingList)
                    {
                        ((List<object>)stateStack[stateStackIndex].value).Add(lastContainedPrimitive);
                    }
                }

                if ((stateStack[stateStackIndex].operation == ParserOperation.ParsingMap && ch == ']') ||
                    (stateStack[stateStackIndex].operation == ParserOperation.ParsingList && ch == ')'))
                {
                    // end of collection
                    string completedCollectionName = stateStack[stateStackIndex].name;
                    object completedCollection = stateStack[stateStackIndex].value;
                    popStack();
                    if (stateStackIndex == -1)
                    {
                        // collection values can be returned immediately if stateStack count = 0 as they have explicit closing tag
                        return completedCollection;
                    }
                    else
                    {
                        if (stateStack[stateStackIndex].operation == ParserOperation.ParsingMap)
                        {
                            ((Dictionary<string, object>)stateStack[stateStackIndex].value)[completedCollectionName] = completedCollection;
                            return null;
                        }
                        else if (stateStack[stateStackIndex].operation == ParserOperation.ParsingList)
                        {
                            ((List<object>)stateStack[stateStackIndex].value).Add(completedCollection);
                            return null;
                        }
                        else
                        {
                            throw new Exception("Non root value without parent collection: " + ch + " state stack: " + stateStack);
                        }
                    }
                }

                if (ch == '(' || ch == '[' || ch == '$' || ch == '@' || ch == '#')
                {
                    // end of primitive value
                    switch (stateStack[stateStackIndex].operation)
                    {
                        case ParserOperation.ParsingString:
                            completedPrimitiveName = stateStack[stateStackIndex].name;
                            completedPrimitive = stateStack[stateStackIndex].encodedValue.ToString();
                            popStack();
                            break;
                        case ParserOperation.ParsingFloat:
                            completedPrimitiveName = stateStack[stateStackIndex].name;
                            completedPrimitive = Convert.ToDouble(stateStack[stateStackIndex].encodedValue.ToString().Replace('.', ','));
                            popStack();
                            break;
                        case ParserOperation.ParsingInt:
                            completedPrimitiveName = stateStack[stateStackIndex].name;
                            completedPrimitive = Convert.ToInt32(stateStack[stateStackIndex].encodedValue.ToString());
                            popStack();
                            break;
                    }

                    if (completedPrimitive != null && stateStackIndex > -1)
                    {
                        // adding completed values to collections
                        if (stateStack[stateStackIndex].operation == ParserOperation.ParsingMap)
                        {
                            ((Dictionary<string, object>)stateStack[stateStackIndex].value)[completedPrimitiveName] = completedPrimitive;
                            completedPrimitive = null;
                        }
                        else if (stateStack[stateStackIndex].operation == ParserOperation.ParsingList)
                        {
                            ((List<object>)stateStack[stateStackIndex].value).Add(completedPrimitive);
                            completedPrimitive = null;
                        }
                        else
                        {
                            throw new Exception("Non root value without parent collection: " + ch + " state stack: " + stateStack);
                        }
                    }
                }

            }

            // start of new value parsing
            if (ch == '(' || ch == '[' || ch == '$' || ch == '@' || ch == '#' || ch == '+')
            {
                ParserState parentState = stateStackIndex > -1 ? stateStack[stateStackIndex] : null;
                switch (ch)
                {
                    case '[': // map
                        stateStackIndex++;
                        stateStack[stateStackIndex].operation = ParserOperation.ParsingMap;
                        stateStack[stateStackIndex].value = new Dictionary<string, object>();                        
                        break;
                    case '(': // list
                        stateStackIndex++;
                        stateStack[stateStackIndex].operation = ParserOperation.ParsingList;
                        stateStack[stateStackIndex].value = new List<object>();  
                        break;
                    case '$': // string
                        stateStackIndex++;
                        stateStack[stateStackIndex].operation = ParserOperation.ParsingString;
                        break;
                    case '@': // int
                        stateStackIndex++;
                        stateStack[stateStackIndex].operation = ParserOperation.ParsingInt;
                        break;
                    case '#': // float
                        stateStackIndex++;
                        stateStack[stateStackIndex].operation = ParserOperation.ParsingFloat;
                        break;
                    case '+': // encoded value
                        stateStackIndex++;
                        stateStack[stateStackIndex].operation = ParserOperation.ParsingEncodedCharacter;
                        break;
                    default:
                        throw new Exception("Value start or encoded character start not handled! character: " + ch + " state stack:" + stateStack);
                }
                if (parentState != null && parentState.operation == ParserOperation.ParsingMap)
                {
                    // starting first name parsing as parent is a map
                    stateStackIndex++;
                    stateStack[stateStackIndex].operation = ParserOperation.ParsingName;
                }
                return completedPrimitive; // returning completed state which may have been completed when new value parsing was started.
            }

            throw new Exception("Unexpected character: " + ch + " state stack: " + stateStack);
        }

        public void popStack()
        {
            stateStack[stateStackIndex].name = ""; 
            stateStack[stateStackIndex].value = null; 
            stateStack[stateStackIndex].encodedValue.Remove(0, stateStack[stateStackIndex].encodedValue.Length); 
            stateStack[stateStackIndex].operation = ParserOperation.None; stateStackIndex--;
        }

        public byte HexCharToByte(char ch)
        {
            switch (ch)
            {
                case 'F': return 15;
                case 'f': return 15;
                case 'E': return 14;
                case 'e': return 14;
                case 'D': return 13;
                case 'd': return 13;
                case 'C': return 12;
                case 'c': return 12;
                case 'B': return 11;
                case 'b': return 11;
                case 'A': return 10;
                case 'a': return 10;
                case '9': return 9;
                case '8': return 8;
                case '7': return 7;
                case '6': return 6;
                case '5': return 5;
                case '4': return 4;
                case '3': return 3;
                case '2': return 2;
                case '1': return 1;
                case '0': return 0;
                default: throw new Exception("Unknown hex character: " + ch);
            }
        }
    }
}

C# Packed Codec Encoder

using System;
using System.Collections.Generic;
using System.Text;

namespace MembraneCore.Network.Connection.Codec.PackedCodec
{
    public class PackedCodecEncoder
    {

        public string Encode(object obj)
        {
            StringBuilder builder = new StringBuilder();
            EncodeObject(builder, obj);
            return builder.ToString();
        }

        private void EncodeObject(StringBuilder builder, object obj)
        {
            object valueBegin = GetValueBeginCharacter(obj.GetType());
            if (valueBegin != null)
            {
                builder.Append(valueBegin.ToString());
            }
            EncodeValue(builder, obj);
            object valueEnd = GetValueEndCharacter(obj.GetType());
            if (valueEnd != null)
            {
                builder.Append(valueEnd.ToString());
            }
        }

        private void EncodeKeyObjectPair(StringBuilder builder, string key, object obj)
        {
            object valueBegin = GetValueBeginCharacter(obj.GetType());
            if (valueBegin != null)
            {
                builder.Append(valueBegin.ToString());
            }
            EncodeString(builder, key);
            builder.Append('=');
            EncodeValue(builder, obj);
            object valueEnd = GetValueEndCharacter(obj.GetType());
            if (valueEnd != null)
            {
                builder.Append(valueEnd.ToString());
            }
        }

        private void EncodeMap(StringBuilder builder, Dictionary<string, object> map)
        {
            foreach (string key in map.Keys)
            {
                EncodeKeyObjectPair(builder, key, map[key]);
            }
        }

        private void EncodeList(StringBuilder builder, List<object> list)
        {
            foreach (object value in list)
            {
                EncodeObject(builder, value);
            }
        }

        private void EncodeValue(StringBuilder builder, object obj)
        {
            Type type = obj.GetType();
            if (type == typeof(Dictionary<string, object>))
            {
                EncodeMap(builder, (Dictionary<string, object>)obj);
                return;
            }
            else if (type == typeof(List<object>))
            {
                EncodeList(builder, (List<object>)obj);
                return;
            }
            else if (type == typeof(string))
            {
                EncodeString(builder, (string)obj);
                return;
            }
            else if (type == typeof(int))
            {
                builder.Append(obj.ToString());
                return;
            }
            else if (type == typeof(double))
            {
                builder.Append(obj.ToString().Replace(',', '.'));
                return;
            }
            else
            {
                throw new Exception("Unsupported type: " + type.ToString());
            }
        }

        private void EncodeString(StringBuilder builder, string str)
        {
            for (int i = 0; i < str.Length; i++)
            {
                char ch = str[i];
                if (ch != '+' && ch != '[' && ch != ']' && ch != '(' && ch != ')' && ch != '@' && ch != '#' && ch != '$' && ch != '=' && ch != '\n' && ch != '\r')
                {
                    builder.Append(ch);
                }
                else
                {
                    EncodeReservedCharacter(builder, ch);
                }
            }
        }

        private void EncodeReservedCharacter(StringBuilder builder, char ch)
        {
            char[] chars = new char[1];
            chars[0] = ch;
            byte[] bytes = Encoding.ASCII.GetBytes(chars);
            byte higherHexValue = (byte)((int)bytes[0] >> 4);
            byte lowererHexValue = (byte)((int)bytes[0] % 16);
            builder.Append("+" + IntToHexChar(higherHexValue) + IntToHexChar(lowererHexValue));
        }

        private object GetValueBeginCharacter(Type type)
        {
            if (type == typeof(Dictionary<string, object>))
            {
                return '[';
            }
            if (type == typeof(List<object>))
            {
                return '(';
            }
            if (type == typeof(string))
            {
                return '$';
            }
            if (type == typeof(int))
            {
                return '@';
            }
            if (type == typeof(double))
            {
                return '#';
            }

            throw new Exception("Unsupported type: " + type.ToString());
        }

        private object GetValueEndCharacter(Type type)
        {
            if (type == typeof(Dictionary<string, object>))
            {
                return ']';
            }
            if (type == typeof(List<object>))
            {
                return ')';
            }
            if (type == typeof(string))
            {
                return null;
            }
            if (type == typeof(int))
            {
                return null;
            }
            if (type == typeof(double))
            {
                return null;
            }

            throw new Exception("Unsupported type: " + type.ToString());
        }

        public char IntToHexChar(byte value)
        {
            switch (value)
            {
                case 15: return 'f';
                case 14: return 'e';
                case 13: return 'd';
                case 12: return 'c';
                case 11: return 'b';
                case 10: return 'a';
                case 9: return '9';
                case 8: return '8';
                case 7: return '7';
                case 6: return '6';
                case 5: return '5';
                case 4: return '4';
                case 3: return '3';
                case 2: return '2';
                case 1: return '1';
                case 0: return '0';
                default: throw new Exception("Not convertible to hex: " + value);
            }
        }

    }
}

C# Packed Codec NUnit Test

using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.Timers;

namespace MembraneCore.Network.Connection.Codec.PackedCodec
{

    [TestFixture]
    public class TestPackedCodec
    {
        [Test]
        public void DecodeEncodeMessage()
        {
            string packedCodecMessage="[@id=17$name=Fred +28the +2b great+29#weight=1.5(args=@1@2@3)]";
            PackedCodecDecoder parser = new PackedCodecDecoder();
            Object value=null;
            for (int i = 0; i < packedCodecMessage.Length; i++)
            {
                value=parser.Decode(packedCodecMessage[i]);
                Assert.IsFalse(value != null && i != packedCodecMessage.Length - 1);
            }

            PackedCodecEncoder encoder = new PackedCodecEncoder();
            string repackedCodecMessage = encoder.Encode(value);
            Assert.AreEqual(packedCodecMessage, repackedCodecMessage);
        }
    }


}