Samples :: Utils

Message Queue Runner

Description

There is a FIX Engine requirement that session listener can't perform time-consuming task. This sample shows how to use message queue and single thread to perform time-consuming tasks outside session listener. This approach is also known as “Producer-Consumer design pattern”.

Usage

  • Run the sample:
    • win: MessageQueueRunner.bat
    • linux: MessageQueueRunner.sh
  • Clean everything:
    • win: clean.bat
    • linux: clean.sh

Source Code

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.engine.Engine;
import biz.onixs.fix.engine.Session;
import biz.onixs.fix.parser.Group;
import biz.onixs.fix.parser.Message;
import biz.onixs.fix.tag.FIX50;
import biz.onixs.fix.tag.Tag;
import biz.onixs.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
  
public class MessageQueueRunner implements Session.InboundApplicationMessageListener {
    private static final Logger LOG = LoggerFactory.getLogger(MessageQueueRunner.class);
    private static final int MSG_NUM = 5;
    private final BlockingQueue<Message> messageQueue = new LinkedBlockingQueue<>();
    private final MessageProcessor messageProcessor = new MessageProcessor(messageQueue);
  
    public void run() {
        Engine.init(-1);
        messageProcessor.start();
        // Emulating inbound application message event
        LOG.info("Messages to process: " + MSG_NUM);
        for (int i = 0; MSG_NUM > i; i++) {
            final Message message = createDemoMessage(i);
            final Session.InboundApplicationMessageArgs args = new Session.InboundApplicationMessageArgs(message);
            onInboundApplicationMessage(this, args);
        }
        Utils.waitForEnterToTerminateApp();
        messageProcessor.stop();
        Engine.getInstance().shutdown();
    }
  
    @Override
    public void onInboundApplicationMessage(final Object sender, final Session.InboundApplicationMessageArgs args) {
        try {
            messageQueue.put(args.getMsg());
        } catch (final InterruptedException e) {
            LOG.error("Error adding message to processing queue: {}", args.getMsg(), e);
        }
    }
  
    private static Message createDemoMessage(final int i) {
        final Message newsMessage = Message.create(FIX50.MsgType.News, Version.FIX50);
        newsMessage.set(Tag.Headline, "Headline " + i);
        final Group linesOfTextGrp = newsMessage.setGroup(Tag.LinesOfText, 1);
        linesOfTextGrp.set(Tag.Text, 0, "Text " + i);
        return newsMessage;
    }
  
    public static void main(final String[] args) {
        try {
            final MessageQueueRunner messageQueueRunner = new MessageQueueRunner();
            messageQueueRunner.run();
        } catch (final Throwable throwable) {
            LOG.error(throwable.getMessage(), throwable);
        }
    }
}

The following class is used in the sample:

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import biz.onixs.fix.parser.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import java.util.concurrent.BlockingQueue;
  
public class MessageProcessor implements Runnable {
    private static final Logger LOG = LoggerFactory.getLogger(MessageProcessor.class);
    private static final long PROCESSING_PAUSE = 1L;
    private final BlockingQueue<Message> messageQueue;
    private Thread thread = null;
    private boolean running = true;
  
    public MessageProcessor(final BlockingQueue<Message> messageQueue) {
        this.messageQueue = messageQueue;
    }
  
    public void start() {
        thread = new Thread(this, "MessageProcessor");
        thread.start();
    }
  
    public void stop() {
        running = false;
        if (!thread.isInterrupted()) {
            thread.interrupt();
        }
    }
  
    public void run() {
        LOG.info("MessageProcessor thread started.");
        while (running) {
            LOG.info("MessageProcessor is checking for messages...");
            try {
                final Message message = messageQueue.take();
                if (running) {
                    process(message);
                }
            } catch (final InterruptedException ignored) {
            }
        }
        LOG.info("MessageProcessor thread stopped.");
    }
  
    private static void process(final Message message) throws InterruptedException {
        LOG.info("Processing message for {} secs: {}", PROCESSING_PAUSE, message);
        Thread.sleep(PROCESSING_PAUSE * 1000L);
        // time-consuming logic goes here
        LOG.info("Processed.");
    }
}

Executor Runner

Description

There is a FIX Engine requirement that session listener can't perform time-consuming task.This sample shows how to use standard thread pool to perform time-consuming tasks outside session listener.

Usage

  • Run the sample:
    • win: ExecutorRunner.bat
    • linux: ExecutorRunner.sh
  • Clean everything:
    • win: clean.bat
    • linux: clean.sh

Source Code

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.engine.Engine;
import biz.onixs.fix.engine.Session;
import biz.onixs.fix.parser.Group;
import biz.onixs.fix.parser.Message;
import biz.onixs.fix.tag.FIX50;
import biz.onixs.fix.tag.Tag;
import biz.onixs.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
  
public class ExecutorRunner implements Session.InboundApplicationMessageListener {
    private static final Logger LOG = LoggerFactory.getLogger(ExecutorRunner.class);
    private static final int MSG_NUM = 5;
    private static final int THREAD_POOL_SIZE = 2;
    private final ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
  
    public void run() {
        Engine.init(-1);
        // Emulating inbound application message event
        LOG.info("Messages to process: " + MSG_NUM);
        for (int i = 0; 5 > i; i++) {
            final Message message = createDemoMessage(i);
            final Session.InboundApplicationMessageArgs args = new Session.InboundApplicationMessageArgs(message);
            onInboundApplicationMessage(this, args);
        }
        Utils.waitForEnterToTerminateApp();
        executorService.shutdownNow();
        Engine.getInstance().shutdown();
    }
  
    @Override
    public void onInboundApplicationMessage(final Object sender, final Session.InboundApplicationMessageArgs args) {
        executorService.submit(new MessageProcessingTask(args.getMsg()));
    }
  
    private static Message createDemoMessage(final int i) {
        final Message newsMessage = Message.create(FIX50.MsgType.News, Version.FIX50);
        newsMessage.set(Tag.Headline, "Headline " + i);
        final Group linesOfTextGrp = newsMessage.setGroup(Tag.LinesOfText, 1);
        linesOfTextGrp.set(Tag.Text, 0, "Text " + i);
        return newsMessage;
    }
  
    public static void main(final String[] args) {
        try {
            final ExecutorRunner executorRunner = new ExecutorRunner();
            executorRunner.run();
        } catch (final Throwable throwable) {
            LOG.error(throwable.getMessage(), throwable);
        }
    }
}

The following class is used in the sample:

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import biz.onixs.fix.parser.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
public class MessageProcessingTask implements Runnable {
    private static final Logger LOG = LoggerFactory.getLogger(MessageProcessingTask.class);
    private static final int PROCESSING_PAUSE = 1;
    private final Message message;
  
    public MessageProcessingTask(final Message message) {
        this.message = message;
    }
  
    public void run() {
        LOG.info("{}: processing message for {} secs: {}", Thread.currentThread(), PROCESSING_PAUSE,
                message);
        // time-consuming logic goes here
        try {
            Thread.sleep((long) (PROCESSING_PAUSE * 1000));
            LOG.info("{}: message processed.", Thread.currentThread());
        } catch (final InterruptedException ignored) {
        }
    }
}

Storage Reader

Description

This sample app demonstrates how to access FIX messages in file session storage.

Usage

  • Run the sample:
    • win: StorageReader.bat
    • linux: StorageReader.sh
  • Clean everything:
    • win: clean.bat
    • linux: clean.sh

Source Code

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.engine.Engine;
import biz.onixs.fix.engine.EngineSettings;
import biz.onixs.fix.engine.SessionId;
import biz.onixs.fix.engine.storage.SessionStorage;
import biz.onixs.fix.engine.storage.StorageRepository;
import biz.onixs.fix.engine.storage.StorageRepositoryManager;
import biz.onixs.fix.parser.FixMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import java.util.List;
  
public class StorageReader {
    private static final Logger LOG = LoggerFactory.getLogger(StorageReader.class);
    private final Version fixVersion = Version.FIX40;
  
    public static void main(final String[] args) {
        final StorageReader storageReader = new StorageReader();
        storageReader.run();
    }
  
    private void run() {
        try {
            final EngineSettings engineSettings = new EngineSettings();
            engineSettings.setStorageFolder("conf/sample/storage");
            Engine.init(engineSettings);
            final StorageRepositoryManager storageRepositoryManager = Engine.getInstance().getStorageRepositoryManager();
            final StorageRepository storageRepository = storageRepositoryManager.getFileStorageRepository();
            final SessionId sessionId = new SessionId("BuySide", "SellSide", fixVersion);
            LOG.info("sessionId={}", sessionId);
            final SessionStorage sessionStorage = storageRepository.create(sessionId, false);
            LOG.info("sessionStorage.getId()='{}'", sessionStorage.getId());
            processStorage(sessionStorage);
            sessionStorage.close(false);
        } catch (final RuntimeException e) {
            LOG.error(e.getMessage().replace("\r", "\\r"), e);
        } finally {
            if (Engine.isInited())
                Engine.getInstance().shutdown();
        }
    }
  
    private static void processStorage(final SessionStorage sessionStorage) {
        LOG.info("Getting outgoing messages...");
        final long outSeqNum = sessionStorage.getOutSeqNum();
        if (0L == outSeqNum) {
            LOG.info("No outgoing messages found in the storage.");
        } else {
            final List<FixMessage> msgs = sessionStorage.getOutboundMessages(1L, outSeqNum);
            for (final FixMessage msg : msgs) {
                processMessage(msg);
            }
        }
    }
  
    private static void processMessage(final FixMessage message) {
        LOG.info("message='{}'", message);
    }
}

Message Fields Iteration

Description

This sample app demonstrates how to iterate over all Message fields.

Usage

  • Run the sample:
    • win: MessageFieldsIteration.bat
    • linux: MessageFieldsIteration.sh
  • Clean everything:
    • win: clean.bat
    • linux: clean.sh

Source Code

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.engine.Engine;
import biz.onixs.fix.parser.Field;
import biz.onixs.fix.parser.FixBlock;
import biz.onixs.fix.parser.Group;
import biz.onixs.fix.parser.GroupInstance;
import biz.onixs.fix.parser.Message;
import biz.onixs.fix.tag.FIX50;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
public class MessageFieldsIteration {
    private static final Logger LOG = LoggerFactory.getLogger(MessageFieldsIteration.class);
  
    private void run() {
        Engine.init(-1);
  
        final Message msg = createMessageWithNestedRepeatingGroups();
  
        iterateOverMessageFields(msg);
  
        Engine.getInstance().shutdown();
    }
  
    private void iterateOverMessageFields(final Message msg) {
        iterateOverFixBlockFields(msg, "");
    }
  
    private void iterateOverFixBlockFields(final FixBlock fixBlock, final String prefix) {
        for (final Field fld : fixBlock) {
            LOG.info("{}{}" + '=' + "{}", prefix, fld.getTag(), fld.get());
            if (fixBlock.hasGroup(fld.getTag())) {
                final Group group = fixBlock.getGroup(fld.getTag());
                iterateOverGroupInstances(group, prefix);
            }
        }
    }
  
    private void iterateOverGroupInstances(final Group group, final String prefix) {
        for (final GroupInstance grpInstance : group) {
            iterateOverFixBlockFields(grpInstance, prefix + "    ");
        }
    }
  
    private static Message createMessageWithNestedRepeatingGroups() {
        final Message msg = Message.create(FIX50.MsgType.NewOrderSingle, Version.FIX50);
        final int CustomTag = 10000;
        msg.set(CustomTag, "CustomTag");
        // Create the repeating group.
        final Group partiesGroup = msg.setGroup(FIX50.Tag.NoPartyIDs, 3);
        // Set the given field in all group instances of the repeating group.
        int i = 0;
        for (final GroupInstance grpInstance : partiesGroup ) {
            grpInstance.set(FIX50.Tag.PartyID, "value" + i++);
        }
        // Create the nested repeating group.
        final Group partiesSubGroup = partiesGroup.setGroup(FIX50.Tag.NoPartySubIDs, 0, 6);
        // Set the given field in all group instances of the nested repeating group.
        i = 0;
        for (final GroupInstance grpInstance : partiesSubGroup) {
            grpInstance.set(FIX50.Tag.PartySubID, "value" + i++);
        }
        //
        LOG.info("Message with nested repeating group: {}", msg);
        //
        return msg;
    }
  
    public static void main(final String[] args) {
        try {
            LOG.info("Message Fields Iteration");
            LOG.info("The application is starting...");
            final MessageFieldsIteration iteration = new MessageFieldsIteration();
            iteration.run();
        } catch (final Throwable throwable) {
            LOG.error(throwable.getMessage(), throwable);
        } finally {
            LOG.info("The application is stopped.");
        }
    }
}

Converter Runner

Description

This sample app demonstrates how to convert Fix message to JSON or XML format.

Usage

  • Run the sample:
    • win: ConverterRunner.bat
    • linux: ConverterRunner.sh
  • Clean everything:
    • win: clean.bat
    • linux: clean.sh

Source Code

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.engine.Engine;
import biz.onixs.fix.fixml.Utils;
import biz.onixs.fix.parser.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
  
import java.io.IOException;
  
public class ConverterRunner {
    private static final Logger LOG = LoggerFactory.getLogger(ConverterRunner.class);
  
    public void run() throws IOException {
        Engine.init();
        //
        final String inFixFile = "sample.converter/MarketDataRequest.txt";
        LOG.info("Loading FIX message from file: {}", inFixFile);
        final Message inFixMessage = Utils.readFixMessage(inFixFile);
        LOG.info("Input FIX message: \n{}", inFixMessage);
        //
        final MessageToJSONConverter fix2Json = new MessageToJSONConverter(Version.FIX44);
        final String jsonMessage = fix2Json.apply(inFixMessage);
        LOG.info("Output converted JSON message: {}{}", System.lineSeparator(), jsonMessage);
        LOG.info("Output converted out of box JSON message: {}{}", System.lineSeparator(), inFixMessage.toJson());
        //
        final MessageToXMLConverter fix2Xml = new MessageToXMLConverter(Version.FIX44);
        final String xmlMessage = fix2Xml.apply(inFixMessage);
        LOG.info("Output converted XML message: {}{}", System.lineSeparator(), xmlMessage);
        LOG.info("Output converted out of box XML message: {}{}", System.lineSeparator(), inFixMessage.toXml());
        //
        Engine.getInstance().shutdown();
    }
  
  
    public static void main(final String[] args) {
        try {
            final ConverterRunner converterRunner = new ConverterRunner();
            converterRunner.run();
        } catch (final Throwable throwable) {
            LOG.error(throwable.getMessage(), throwable);
        }
    }
}

The following classes are used in the sample:

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
import biz.onixs.fix.dictionary.ContainerDefinition;
import biz.onixs.fix.dictionary.Dictionary;
import biz.onixs.fix.dictionary.DictionaryManager;
import biz.onixs.fix.dictionary.FieldDefinition;
import biz.onixs.fix.dictionary.GroupDefinition;
import biz.onixs.fix.dictionary.MessageDefinition;
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.parser.Field;
import biz.onixs.fix.parser.FixBlock;
import biz.onixs.fix.parser.Group;
import biz.onixs.fix.parser.GroupInstance;
import biz.onixs.fix.parser.Message;
import biz.onixs.util.ValuePtr;
  
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Function;
  
public class MessageToJSONConverter implements Function<Message, String> {
    private static final String PREFIX = "    ";
    private static final String SPACES = System.lineSeparator() + " \t";
    //List of the Header tags according to the FIX5.0SP2 specification
    private static final Integer[] header =  {8, 9, 35, 49, 56, 115, 128, 90, 91, 34, 50, 142, 57, 143, 116,
            144, 129, 145, 43, 97, 52, 122, 212, 213, 347, 369, 1128, 1129, 1156, 627, 628, 629, 630};
    private static final Collection<Integer> headerTags = new HashSet<>(Arrays.asList(header));
    //List of the Trailer tags according to the FIX5.0SP2 specification
    private static final Integer[] trailer = {93, 89, 10};
    private static final Collection<Integer> trailerTags = new HashSet<>(Arrays.asList(trailer));
    private final Dictionary dictionary;
  
    MessageToJSONConverter(final Version fixVersion) {
        dictionary = DictionaryManager.getDictionary(fixVersion);
    }
  
    public static String info() {
        return "Info";
    }
  
    @Override
    public String apply(final Message message) {
        final StringBuilder strBdr = new StringBuilder();
        final MessageDefinition msgDef = dictionary.get(new ValuePtr(message.getType()));
        messageToJson(message, msgDef, strBdr);
        return strBdr.toString();
    }
  
    private static StringBuilder deleteLastCommaIfPresent(final StringBuilder bdr) {
        for(int i = bdr.length() - 1; 0 <= i; i--) {
            final char c = bdr.charAt(i);
            if (0 > SPACES.indexOf(c)) {
                if (',' == c) {
                    return bdr.deleteCharAt(i);
                } else {
                    break;
                }
            }
        }
        return bdr;
    }
  
    private static void messageToJson(final FixBlock msg, final MessageDefinition def, final StringBuilder strBdr) {
        final StringBuilder headerBdr = new StringBuilder(PREFIX + "\"Header\": {" + System.lineSeparator());
        final StringBuilder bodyBdr = new StringBuilder(PREFIX + "\"Body\": {" + System.lineSeparator());
        final StringBuilder trailerBdr = new StringBuilder(PREFIX + "\"Trailer\": {" + System.lineSeparator());
        for (final Field fld : msg) {
            final int tag = fld.getTag();
            final StringBuilder curBdr = headerTags.contains(tag) ? headerBdr :
                                            (trailerTags.contains(tag) ? trailerBdr : bodyBdr);
            fieldToJson(msg, def, fld, curBdr, PREFIX);
            curBdr.append(",").append(System.lineSeparator());
        }
        deleteLastCommaIfPresent(headerBdr).append(PREFIX).append("},").append(System.lineSeparator());
        deleteLastCommaIfPresent(bodyBdr).append(PREFIX).append("},").append(System.lineSeparator());
        deleteLastCommaIfPresent(trailerBdr).append(PREFIX).append("}").append(System.lineSeparator());
        strBdr.append("{").append(System.lineSeparator());
        strBdr.append(headerBdr);
        strBdr.append(bodyBdr);
        strBdr.append(trailerBdr);
        strBdr.append("}");
    }
  
    private static void fixBlockToJson(final FixBlock fixBlk, final ContainerDefinition def,
                                       final StringBuilder strBdr, final String prefix) {
        strBdr.append(prefix).append("{").append(System.lineSeparator());
        for (final Iterator<Field> iterator = fixBlk.iterator(); iterator.hasNext(); ) {
            final Field fld = iterator.next();
            fieldToJson(fixBlk, def, fld, strBdr, prefix);
            trailingComma(strBdr, iterator.hasNext());
        }
        strBdr.append(prefix).append("}");
    }
  
    private static void fieldToJson(final FixBlock fixBlk, final ContainerDefinition def, final Field fld,
                                    final StringBuilder strBdr, final String prefix) {
        final int tag = fld.getTag();
        final FieldDefinition fldDef = def.getFieldByTag(tag);
        if (fldDef.isGroupDefined()) {
            final Group grp = fixBlk.getGroup(tag);
            if (null != grp) {
                groupToJson(grp, fldDef, strBdr, prefix + PREFIX);
            }
        } else {
            final String name = getFieldName(fldDef);
            final String value = fld.get();
            strBdr.append(prefix).append(PREFIX).append("\"").append(name).append("\": \"").append(value).append("\"");
        }
    }
  
    private static void groupToJson(final Iterable<GroupInstance> grp, final FieldDefinition leadingFldDef,
                                    final StringBuilder strBdr, final String prefix) {
        final String name = getFieldName(leadingFldDef);
        strBdr.append(prefix).append("\"").append(name).append("\": [").append(System.lineSeparator());
        final GroupDefinition grpDef = leadingFldDef.getGroupDefinition();
        for (final Iterator<GroupInstance> it = grp.iterator(); it.hasNext(); ) {
            final GroupInstance grpInst = it.next();
            fixBlockToJson(grpInst, grpDef, strBdr, prefix);
            trailingComma(strBdr, it.hasNext());
        }
        strBdr.append(prefix).append("]");
    }
  
    private static void trailingComma(final StringBuilder strBdr, final boolean isCommaRequired) {
        if (isCommaRequired) {
            strBdr.append(",");
        }
        strBdr.append(System.lineSeparator());
    }
  
    private static String getFieldName(final FieldDefinition def) {
        return (null != def.getTagNameBytes()) ? new String(def.getTagNameBytes(), StandardCharsets.UTF_8) : "undefined";
    }
}
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import biz.onixs.fix.dictionary.ContainerDefinition;
import biz.onixs.fix.dictionary.Dictionary;
import biz.onixs.fix.dictionary.DictionaryManager;
import biz.onixs.fix.dictionary.FieldDefinition;
import biz.onixs.fix.dictionary.GroupDefinition;
import biz.onixs.fix.dictionary.MessageDefinition;
import biz.onixs.fix.dictionary.Version;
import biz.onixs.fix.parser.Field;
import biz.onixs.fix.parser.FixBlock;
import biz.onixs.fix.parser.Group;
import biz.onixs.fix.parser.GroupInstance;
import biz.onixs.fix.parser.Message;
import biz.onixs.util.ValuePtr;
  
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
  
public class MessageToXMLConverter implements Function<Message, String> {
    private static final String PREFIX = "    ";
    private final Dictionary dictionary;
  
    MessageToXMLConverter(final Version fixVersion) {
        dictionary = DictionaryManager.getDictionary(fixVersion);
    }
  
    @Override
    public String apply(final Message message) {
        final StringBuilder strBdr = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        strBdr.append(System.lineSeparator()).append(System.lineSeparator());
        strBdr.append("<XML>").append(System.lineSeparator());
        final MessageDefinition msgDef = dictionary.get(new ValuePtr(message.getType()));
        strBdr.append(PREFIX).append("<message name=\"").append(msgDef.getMsgName()).append("\"/>");
        strBdr.append(System.lineSeparator());
        fixBlock2xml(message, msgDef, strBdr, PREFIX + PREFIX);
        strBdr.append(PREFIX).append("</message>").append(System.lineSeparator());
        strBdr.append("</XML>");
        return strBdr.toString();
    }
  
    private static void fixBlock2xml(final FixBlock fixBlk, final ContainerDefinition def,
                              final StringBuilder strBdr, final String prefix) {
        for (final Field fld : fixBlk) {
            final int tag = fld.getTag();
            final FieldDefinition fldDef = def.getFieldByTag(tag);
            if (fldDef.isGroupDefined()) {
                final Group grp = fixBlk.getGroup(tag);
                if (null != grp) {
                    group2xml(grp, fldDef, strBdr, prefix);
                }
            } else {
                final String name = getFieldName(fldDef);
                final String value = fld.get();
                strBdr.append(prefix).append("<field name=\"").append(name).append("\" tag=\"").append(tag)
                        .append("\" value=\"").append(value).append("\"/>").append(System.lineSeparator());
            }
        }
    }
  
    private static void group2xml(final Group grp, final FieldDefinition leadingFldDef,
                                  final StringBuilder strBdr, final String prefix) {
        final String name = getFieldName(leadingFldDef);
        final int tag = leadingFldDef.getTag();
        final int numberOfInstances = grp.getNumberOfInstances();
        strBdr.append(prefix).append("<group name=\"").append(name).append("\" tag=\"").append(tag)
                .append("\" numberofinstances=\"").append(numberOfInstances)
                .append("\">").append(System.lineSeparator());
        final GroupDefinition grpDef = leadingFldDef.getGroupDefinition();
        int index = 0;
        for (final GroupInstance grpInst :  grp) {
            groupInstance2xml(grpInst, index++, grpDef, strBdr, prefix + PREFIX);
        }
        strBdr.append(prefix).append("</group>").append(System.lineSeparator());
    }
  
    private static void groupInstance2xml(final FixBlock grpInst, final int index, final ContainerDefinition def,
                                          final StringBuilder strBdr, final String prefix) {
        strBdr.append(prefix).append("<instance index=\"").append(index).append("\">").append(System.lineSeparator());
        fixBlock2xml(grpInst, def, strBdr, prefix + PREFIX);
        strBdr.append(prefix).append("</instance>").append(System.lineSeparator());
    }
  
    private static String getFieldName(final FieldDefinition def) {
        return (null != def.getTagNameBytes()) ?
                new String(def.getTagNameBytes(), StandardCharsets.UTF_8) : Integer.toString(def.getTag());
    }
}