![JAR search and dependency download from the Maven repository](/logo.png)
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