All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fixprotocol.orchestra.quickfix.DataDictionaryGenerator Maven / Gradle / Ivy

/*
 * Copyright 2017-2020 FIX Protocol Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 *
 */
package io.fixprotocol.orchestra.quickfix;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import io.fixprotocol._2020.orchestra.repository.CodeSetType;
import io.fixprotocol._2020.orchestra.repository.CodeType;
import io.fixprotocol._2020.orchestra.repository.ComponentRefType;
import io.fixprotocol._2020.orchestra.repository.ComponentType;
import io.fixprotocol._2020.orchestra.repository.FieldRefType;
import io.fixprotocol._2020.orchestra.repository.FieldType;
import io.fixprotocol._2020.orchestra.repository.GroupRefType;
import io.fixprotocol._2020.orchestra.repository.GroupType;
import io.fixprotocol._2020.orchestra.repository.MessageType;
import io.fixprotocol._2020.orchestra.repository.PresenceT;
import io.fixprotocol._2020.orchestra.repository.Repository;

/**
 * Generates a QuickFIX data dictionary from a FIX Orchestra file
 * 

* This format is consumable by the C++, Java and .NET versions of QuickFIX. * * @author Don Mendelson * */ public class DataDictionaryGenerator { private static class KeyValue { final String key; final T value; public KeyValue(String key, T value) { this.key = key; this.value = value; } } private static final int SPACES_PER_LEVEL = 2; /** * Runs a DataDictionaryGenerator with command line arguments *

* The data dictionary format is consumable by QuickFIX, QuickFIX/J and QuickFIX/n. * * @param args command line arguments. The first argument is the name of a FIX Orchestra file. An * optional second argument is the target directory for generated files. It defaults to * directory "spec". * @throws IOException an IO error occurred * @throws JAXBException an XML parsing error occurred */ public static void main(String[] args) throws IOException, JAXBException { final DataDictionaryGenerator generator = new DataDictionaryGenerator(); if (args.length >= 1) { final File inputFile = new File(args[0]); File outputDir; if (args.length >= 2) { outputDir = new File(args[1]); } else { outputDir = new File("spec"); } try (FileInputStream inputStream = new FileInputStream(inputFile)) { generator.generate(inputStream, outputDir); } } else { generator.usage(); } } private final Map codeSets = new HashMap<>(); private final Map components = new HashMap<>(); private final Map groups = new HashMap<>(); private final Map fields = new HashMap<>(); public void generate(InputStream inputFile, File outputDir) throws JAXBException, IOException { final Repository repository = unmarshal(inputFile); generate(repository, outputDir); } public void generate(Repository repository, File outputDir) throws IOException { final List codeSetList = repository.getCodeSets().getCodeSet(); for (final CodeSetType codeSet : codeSetList) { codeSets.put(codeSet.getName(), codeSet); } final List componentList = repository.getComponents().getComponent(); for (final ComponentType component : componentList) { components.put(component.getId().intValue(), component); } final List groupList = repository.getGroups().getGroup(); for (final GroupType group : groupList) { groups.put(group.getId().intValue(), group); } final List fieldList = repository.getFields().getField(); for (final FieldType fieldType : fieldList) { fields.put(fieldType.getId().intValue(), fieldType); } String version = repository.getVersion(); // Split off EP portion of version in the form "FIX.5.0SP2_EP216" final String[] parts = version.split("_"); if (parts.length > 0) { version = parts[0]; } int major = 0; int minor = 0; final String regex = "(FIX\\.)(?\\d+)(\\.)(?\\d+)(.*)"; final Pattern pattern = Pattern.compile(regex); final Matcher matcher = pattern.matcher(version); if (matcher.find()) { major = Integer.parseInt(matcher.group("major")); minor = Integer.parseInt(matcher.group("minor")); final String versionPath = version.replaceAll("[\\.]", ""); final File file = getSpecFilePath(outputDir, versionPath, ".xml"); outputDir.mkdirs(); try (FileWriter writer = new FileWriter(file)) { writeElement(writer, "fix", 0, false, new KeyValue("major", major), new KeyValue("minor", minor)); writeElement(writer, "header", 1, true); writeElement(writer, "trailer", 1, true); writeElement(writer, "messages", 1, false); final List messageList = repository.getMessages().getMessage(); for (final MessageType messageType : messageList) { writeMessage(writer, messageType); } writeElementEnd(writer, "messages", 1); writeElement(writer, "components", 1, false); for (final ComponentType componentType : componentList) { writeComponent(writer, componentType); } for (final GroupType groupType : groupList) { writeGroup(writer, groupType); } writeElementEnd(writer, "components", 1); writeElement(writer, "fields", 1, false); for (final FieldType fieldType : fieldList) { writeField(writer, fieldType); } writeElementEnd(writer, "fields", 1); writeElementEnd(writer, "fix", 0); } } else { System.err.format("Failed to parse FIX major and minor version in %s%n", version); } } private File getSpecFilePath(File outputDir, String versionPath, String extension) { final StringBuilder sb = new StringBuilder(); sb.append(versionPath); sb.append(extension); return new File(outputDir, sb.toString()); } private String indent(int level) { final char[] chars = new char[level * SPACES_PER_LEVEL]; Arrays.fill(chars, ' '); return new String(chars); } private boolean isAdmin(String category) { return category != null && category.equals("Session"); } private String toConstantName(String symbolicName) { final StringBuilder sb = new StringBuilder(symbolicName); for (int i = symbolicName.length() - 1; i > 0; i--) { if (Character.isUpperCase(sb.charAt(i)) && !Character.isUpperCase(sb.charAt(i - 1))) { sb.insert(i, '_'); } } return sb.toString().toUpperCase(); } private Repository unmarshal(InputStream inputFile) throws JAXBException { final JAXBContext jaxbContext = JAXBContext.newInstance(Repository.class); final Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); return (Repository) jaxbUnmarshaller.unmarshal(inputFile); } private void usage() { System.out.format("Usage: java %s ", this.getClass().getName()); } private Writer writeCode(Writer writer, CodeType code) throws IOException { writeElement(writer, "value", 3, true, new KeyValue("enum", code.getValue()), new KeyValue("description", toConstantName(code.getName()))); return writer; } private Writer writeComponent(Writer writer, ComponentRefType componentRefType) throws IOException { final ComponentType component = components.get(componentRefType.getId().intValue()); writeElement(writer, "component", 3, true, new KeyValue("name", component.getName()), new KeyValue("required", componentRefType.getPresence().equals(PresenceT.REQUIRED) ? "Y" : "N")); return writer; } private Writer writeComponent(Writer writer, ComponentType componentType) throws IOException { writeElement(writer, "component", 2, false, new KeyValue("name", componentType.getName())); final List members = componentType.getComponentRefOrGroupRefOrFieldRef(); for (final Object member : members) { if (member instanceof FieldRefType) { final FieldRefType fieldRefType = (FieldRefType) member; writeField(writer, fieldRefType); } else if (member instanceof GroupRefType) { final GroupRefType groupRefType = (GroupRefType) member; writeGroup(writer, groupRefType); } else if (member instanceof ComponentRefType) { final ComponentRefType componentRefType = (ComponentRefType) member; writeComponent(writer, componentRefType); } } writeElementEnd(writer, "component", 2); return writer; } private Writer writeElement(Writer writer, String name, int level, boolean isEmpty) throws IOException { writer.write(String.format("%s<%s", indent(level), name)); if (isEmpty) { writer.write("/>\n"); } else { writer.write(">\n"); } return writer; } private Writer writeElement(Writer writer, String name, int level, boolean isEmpty, KeyValue... attributes) throws IOException { writer.write(String.format("%s<%s", indent(level), name)); for (int i = 0; i < attributes.length; i++) { writer.write(String.format(" %s=\"%s\"", attributes[i].key, attributes[i].value.toString())); } if (isEmpty) { writer.write("/>\n"); } else { writer.write(">\n"); } return writer; } private Writer writeElementEnd(Writer writer, String name, int level) throws IOException { writer.write(String.format("%s%n", indent(level), name)); return writer; } private Writer writeField(Writer writer, FieldRefType fieldRefType) throws IOException { final FieldType field = fields.get(fieldRefType.getId().intValue()); writeElement(writer, "field", 3, true, new KeyValue("name", field.getName()), new KeyValue("required", fieldRefType.getPresence().equals(PresenceT.REQUIRED) ? "Y" : "N")); return writer; } private Writer writeField(Writer writer, FieldType fieldType) throws IOException { final String type = fieldType.getType(); final CodeSetType codeSet = codeSets.get(type); final String fixType = codeSet == null ? type : codeSet.getType(); writeElement(writer, "field", 2, codeSet == null, new KeyValue("number", fieldType.getId().intValue()), new KeyValue("name", fieldType.getName()), new KeyValue("type", fixType.toUpperCase())); if (codeSet != null) { for (final CodeType code : codeSet.getCode()) { writeCode(writer, code); } writeElementEnd(writer, "field", 2); } return writer; } private Writer writeGroup(Writer writer, GroupRefType groupRefType) throws IOException { final GroupType group = groups.get(groupRefType.getId().intValue()); writeElement(writer, "component", 3, true, new KeyValue("name", group.getName()), new KeyValue("required", groupRefType.getPresence().equals(PresenceT.REQUIRED) ? "Y" : "N")); return writer; } private Writer writeGroup(Writer writer, GroupType groupType) throws IOException { writeElement(writer, "component", 2, false, new KeyValue("name", groupType.getName())); final FieldType numInGroupField = fields.get(groupType.getNumInGroup().getId().intValue()); writeElement(writer, "group", 3, false, new KeyValue("name", numInGroupField.getName())); final List members = groupType.getComponentRefOrGroupRefOrFieldRef(); for (final Object member : members) { if (member instanceof FieldRefType) { final FieldRefType fieldRefType = (FieldRefType) member; writeField(writer, fieldRefType); } else if (member instanceof GroupRefType) { final GroupRefType groupRefType = (GroupRefType) member; writeGroup(writer, groupRefType); } else if (member instanceof ComponentRefType) { final ComponentRefType componentRefType = (ComponentRefType) member; writeComponent(writer, componentRefType); } } writeElementEnd(writer, "group", 3); writeElementEnd(writer, "component", 2); return writer; } private Writer writeMessage(Writer writer, MessageType messageType) throws IOException { final boolean isAdminMessage = isAdmin(messageType.getCategory()); final String msgcat = isAdminMessage ? "admin" : "app"; writeElement(writer, "message", 2, false, new KeyValue("name", messageType.getName()), new KeyValue("msgtype", messageType.getMsgType()), new KeyValue("msgcat", msgcat)); final List members = messageType.getStructure().getComponentRefOrGroupRefOrFieldRef(); for (final Object member : members) { if (member instanceof FieldRefType) { final FieldRefType fieldRefType = (FieldRefType) member; writeField(writer, fieldRefType); } else if (member instanceof GroupRefType) { final GroupRefType groupRefType = (GroupRefType) member; writeGroup(writer, groupRefType); } else if (member instanceof ComponentRefType) { final ComponentRefType componentRefType = (ComponentRefType) member; final ComponentType componentType = components.get(((ComponentRefType) member).getId().intValue()); if (!isAdminMessage && !isAdmin(componentType.getCategory())) { writeComponent(writer, componentRefType); } } } writeElementEnd(writer, "message", 2); return writer; } }