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

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

package io.fixprotocol.orchestra.quickfix;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import io.fixprotocol._2016.fixrepository.CodeSetType;
import io.fixprotocol._2016.fixrepository.CodeSets;
import io.fixprotocol._2016.fixrepository.CodeType;
import io.fixprotocol._2016.fixrepository.ComponentRefType;
import io.fixprotocol._2016.fixrepository.ComponentType;
import io.fixprotocol._2016.fixrepository.FieldRefType;
import io.fixprotocol._2016.fixrepository.FieldType;
import io.fixprotocol._2016.fixrepository.GroupRefType;
import io.fixprotocol._2016.fixrepository.GroupType;
import io.fixprotocol._2016.fixrepository.MessageType;
import io.fixprotocol._2016.fixrepository.Protocol;
import io.fixprotocol._2016.fixrepository.Repository;

/**
 * Generates message classes for QuickFIX/J from a FIX Orchestra file
 * 

* Unlike the QuickFIX/J code generator, this utility works directly from a FIX Orchestra or FIX * Repository 2016 Edition file rather than from a QuickFIX data dictionary file. *

* For now, message validation in QuickFIX/J still requires a data dictionary file, but in future * versions, validations may be delegated to additional generated code that takes advantage to * conditional logic supported by Orchestra. For example, a validator may invoke an evaluation of an * expression for a conditionally required field. * * @author Don Mendelson * */ public class CodeGeneratorJ { private static final List DATE_TYPES = Arrays.asList("UTCTimestamp", "UTCTimeOnly", "UTCDateOnly", "LocalMktDate", "LocalMktTime"); private static final String FIELD_PACKAGE = "quickfix.field"; private static final long SERIALIZATION_VERSION = 552892318L; private static final int SPACES_PER_LEVEL = 2; /** * Runs a CodeGeneratorJ with command line arguments * * @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 code. It defaults to * "target/generated-sources". */ public static void main(String[] args) { CodeGeneratorJ generator = new CodeGeneratorJ(); if (args.length >= 1) { File inputFile = new File(args[0]); File outputDir; if (args.length >= 2) { outputDir = new File(args[1]); } else { outputDir = new File("target/generated-sources"); } generator.generate(inputFile, outputDir); } else { generator.usage(); } } private final Map codeSets = new HashMap<>(); private final Map components = new HashMap<>(); private final Map fields = new HashMap<>(); private final Map groups = new HashMap<>(); public void generate(File inputFile, File outputDir) { try { final Repository repository = unmarshal(inputFile); final List codeSetsList = repository.getCodeSets(); for (CodeSets codeSetsCollection : codeSetsList) { final List codeSetList = codeSetsCollection.getCodeSet(); for (CodeSetType codeSet : codeSetList) { codeSets.put(codeSet.getName(), codeSet); } } final List fieldList = repository.getFields().getField(); final File fileDir = getPackagePath(outputDir, FIELD_PACKAGE); fileDir.mkdirs(); for (FieldType fieldType : fieldList) { fields.put(fieldType.getId().intValue(), fieldType); generateField(outputDir, fieldType, FIELD_PACKAGE); } final List protocols = repository.getProtocol(); for (Protocol protocol : protocols) { String version = protocol.getVersion(); // Split off EP portion of version String[] parts = version.split("_"); if (parts.length > 0) { version = parts[0]; } String versionPath = version.replaceAll("[\\.]", "").toLowerCase(); final String componentPackage = getPackage("quickfix", versionPath, "component"); final File componentDir = getPackagePath(outputDir, componentPackage); componentDir.mkdirs(); final List componentList = protocol.getComponents().getComponentOrGroup(); for (ComponentType component : componentList) { if (component instanceof GroupType) { groups.put(component.getId().intValue(), (GroupType) component); } else { components.put(component.getId().intValue(), component); } } for (ComponentType component : componentList) { if (component instanceof GroupType) { generateGroup(outputDir, (GroupType) component, componentPackage); } else if (protocol.isHasComponents()) { generateComponent(outputDir, component, componentPackage); } } final String messagePackage = getPackage("quickfix", versionPath); final File messageDir = getPackagePath(outputDir, messagePackage); messageDir.mkdirs(); final List messageList = protocol.getMessages().getMessage(); for (MessageType message : messageList) { generateMessage(outputDir, message, messagePackage, componentPackage); } generateMessageBaseClass(outputDir, version, messagePackage); generateMessageFactory(outputDir, messagePackage, messageList); generateMessageCracker(outputDir, messagePackage, messageList); } } catch (JAXBException | IOException e) { e.printStackTrace(); } } private void generateComponent(File outputDir, ComponentType componentType, String packageName) throws IOException { String name = toTitleCase(componentType.getName()); File file = getClassFilePath(outputDir, packageName, name); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, packageName); writeImport(writer, "quickfix.FieldNotFound"); writeImport(writer, "quickfix.Group"); writeClassDeclaration(writer, name, "quickfix.MessageComponent"); writeSerializationVersion(writer, SERIALIZATION_VERSION); writeMsgType(writer, ""); List componentFields = new ArrayList<>(); List members = componentType.getComponentRefOrGroupRefOrFieldRef(); componentFields.addAll(members.stream().filter(member -> member instanceof FieldRefType) .map(member -> ((FieldRefType) member).getId().intValue()).collect(Collectors.toList())); writeComponentFieldIds(writer, componentFields); List componentGroupFields = new ArrayList<>(); writeGroupFieldIds(writer, componentGroupFields); writeComponentNoArgConstructor(writer, name); writeMemberAccessors(writer, members, packageName, packageName); writeEndClassDeclaration(writer); } } private void generateField(File outputDir, FieldType fieldType, String packageName) throws IOException { String name = toTitleCase(fieldType.getName()); File file = getClassFilePath(outputDir, packageName, name); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, packageName); String type = fieldType.getType(); CodeSetType codeSet = codeSets.get(type); String fixType = codeSet == null ? type : codeSet.getType(); if (DATE_TYPES.contains(fixType)) { writeImport(writer, "java.util.Date"); } String baseClassname = getFieldBaseClass(fixType); if (baseClassname.equals("DecimalField")) { writeImport(writer, "java.math.BigDecimal"); } String qualifiedBaseClassname = getQualifiedClassName("quickfix", baseClassname); writeImport(writer, qualifiedBaseClassname); writeClassDeclaration(writer, name, baseClassname); writeSerializationVersion(writer, SERIALIZATION_VERSION); int fieldId = fieldType.getId().intValue(); writeFieldId(writer, fieldId); if (codeSet != null) { writeValues(writer, codeSet); } writeFieldNoArgConstructor(writer, name, fieldId); writeFieldArgConstructor(writer, name, fieldId, baseClassname); writeEndClassDeclaration(writer); } } private void generateGroup(File outputDir, GroupType groupType, String packageName) throws IOException { String name = toTitleCase(groupType.getName()); File file = getClassFilePath(outputDir, packageName, name); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, packageName); writeImport(writer, "quickfix.FieldNotFound"); writeImport(writer, "quickfix.Group"); writeClassDeclaration(writer, name, "quickfix.MessageComponent"); writeSerializationVersion(writer, SERIALIZATION_VERSION); writeMsgType(writer, ""); List componentFields = Collections.emptyList(); writeComponentFieldIds(writer, componentFields); int numInGroupId = groupType.getNumInGroupId().intValue(); List componentGroupFields = new ArrayList<>(); componentGroupFields.add(numInGroupId); writeGroupFieldIds(writer, componentGroupFields); writeComponentNoArgConstructor(writer, name); FieldType numInGroupField = fields.get(numInGroupId); String numInGroupFieldName = numInGroupField.getName(); writeFieldAccessors(writer, numInGroupFieldName, numInGroupId); writeGroupInnerClass(writer, groupType, packageName, packageName); List members = groupType.getComponentRefOrGroupRefOrFieldRef(); writeMemberAccessors(writer, members, packageName, packageName); writeEndClassDeclaration(writer); } } private void generateMessage(File outputDir, MessageType messageType, String messagePackage, String componentPackage) throws IOException { String messageClassname = toTitleCase(messageType.getName()); String scenario = messageType.getScenario(); if (!scenario.equals("base")) { messageClassname = messageClassname + toTitleCase(scenario); } File file = getClassFilePath(outputDir, messagePackage, messageClassname); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, messagePackage); writeImport(writer, "quickfix.FieldNotFound"); writeImport(writer, "quickfix.field.*"); writeImport(writer, "quickfix.Group"); writeClassDeclaration(writer, messageClassname, "Message"); writeSerializationVersion(writer, SERIALIZATION_VERSION); writeMsgType(writer, messageType.getMsgType()); List members = messageType.getStructure().getComponentOrComponentRefOrGroup(); writeMessageNoArgConstructor(writer, messageClassname); writeMemberAccessors(writer, members, messagePackage, componentPackage); writeEndClassDeclaration(writer); } } private void generateMessageBaseClass(File outputDir, String version, String messagePackage) throws IOException { File file = getClassFilePath(outputDir, messagePackage, "Message"); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, messagePackage); writeImport(writer, "quickfix.field.*"); writeClassDeclaration(writer, "Message", "quickfix.Message"); writeSerializationVersion(writer, SERIALIZATION_VERSION); writeMessageNoArgBaseConstructor(writer, "Message"); writeProtectedMessageBaseConstructor(writer, "Message", getBeginString(version)); writeMessageDerivedHeaderClass(writer); writeEndClassDeclaration(writer); } } private void generateMessageCracker(File outputDir, String messagePackage, List messageList) throws IOException { File file = getClassFilePath(outputDir, messagePackage, "MessageCracker"); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, messagePackage); writeImport(writer, "quickfix.*"); writeImport(writer, "quickfix.field.*"); writeClassDeclaration(writer, "MessageCracker"); writer.write(String.format("%n%spublic void onMessage(quickfix.Message message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {%n", indent(1))); writer.write(String.format("%sthrow new UnsupportedMessageType();%n", indent(2))); writer.write(String.format("%s}%n", indent(1))); for (MessageType messageType : messageList) { String name = messageType.getName(); String scenario = messageType.getScenario(); if (!scenario.equals("base")) { continue; } writer.write(String.format("%s/**%n", indent(1))); writer.write(String.format("%s * Callback for %s message.%n", indent(1), name)); writer.write(String.format("%s * @param message%n", indent(1))); writer.write(String.format("%s * @param sessionID%n", indent(1))); writer.write(String.format("%s * @throws FieldNotFound%n", indent(1))); writer.write(String.format("%s * @throws UnsupportedMessageType%n", indent(1))); writer.write(String.format("%s * @throws IncorrectTagValue%n", indent(1))); writer.write(String.format("%s */%n", indent(1))); writer.write(String.format( "%n%spublic void onMessage(%s message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {%n", indent(1), name)); writer.write(String.format("%sthrow new UnsupportedMessageType();%n", indent(2))); writer.write(String.format("%s}%n", indent(1))); } String crackMethodName = "crack" + messagePackage.split("\\.")[1]; writer.write(String.format( "%n%spublic void crack(quickfix.Message message, SessionID sessionID)%n", indent(1))); writer.write(String.format( "%sthrows UnsupportedMessageType, FieldNotFound, IncorrectTagValue {%n", indent(2))); writer.write( String.format("%s%s((Message) message, sessionID);%n", indent(2), crackMethodName)); writer.write(String.format("%s}%n", indent(1))); writer.write(String.format("%n%spublic void %s(Message message, SessionID sessionID)%n", indent(1), crackMethodName)); writer.write(String.format( "%sthrows UnsupportedMessageType, FieldNotFound, IncorrectTagValue {%n", indent(2))); writer.write(String.format("%sString type = message.getHeader().getString(MsgType.FIELD);%n", indent(2))); writer.write(String.format("%sswitch (type) {%n", indent(2))); for (MessageType messageType : messageList) { String name = messageType.getName(); String scenario = messageType.getScenario(); if (!scenario.equals("base")) { continue; } writer.write(String.format("%scase %s.MSGTYPE:%n", indent(2), name)); writer.write(String.format("%sonMessage((%s)message, sessionID);%n%sbreak;%n", indent(3), name, indent(3))); } writer.write(String.format("%sdefault:%n%sonMessage(message, sessionID);%n%s}%n%s}%n", indent(2), indent(3), indent(2), indent(1))); writeEndClassDeclaration(writer); } } private void generateMessageFactory(File outputDir, String messagePackage, List messageList) throws IOException { File file = getClassFilePath(outputDir, messagePackage, "MessageFactory"); try (FileWriter writer = new FileWriter(file)) { writeFileHeader(writer); writePackage(writer, messagePackage); writeImport(writer, "quickfix.Message"); writeImport(writer, "quickfix.Group"); writer.write(String.format("%npublic class %s implements %s {%n", "MessageFactory", "quickfix.MessageFactory")); writeMessageCreateMethod(writer, messageList, messagePackage); writeGroupCreateMethod(writer, messageList, messagePackage); writeEndClassDeclaration(writer); } } private String getBeginString(String version) { if (version.startsWith("FIX.5")) { return "FIXT.1.1"; } else { return version; } } private File getClassFilePath(File outputDir, String packageName, String className) { StringBuilder sb = new StringBuilder(); sb.append(packageName.replace('.', File.separatorChar)); sb.append(File.separatorChar); sb.append(className); sb.append(".java"); return new File(outputDir, sb.toString()); } private String getFieldBaseClass(String type) { String baseType; switch (type) { case "char": baseType = "CharField"; break; case "Price": case "Amt": case "Qty": case "PriceOffset": baseType = "DecimalField"; break; case "int": case "NumInGroup": case "SeqNum": case "Length": case "TagNum": case "DayOfMonth": baseType = "IntField"; break; case "UTCTimestamp": baseType = "UtcTimeStampField"; break; case "UTCTimeOnly": case "LocalMktTime": baseType = "UtcTimeOnlyField"; break; case "UTCDateOnly": case "LocalMktDate": baseType = "UtcDateOnlyField"; break; case "Boolean": baseType = "BooleanField"; break; case "float": case "Percentage": baseType = "DoubleField"; break; default: baseType = "StringField"; } return baseType; } private void getGroupFields(ComponentType group, List groupComponentFields) { List members = group.getComponentRefOrGroupRefOrFieldRef(); for (Object member : members) { if (member instanceof FieldRefType) { groupComponentFields.add(((FieldRefType) member).getId().intValue()); } else if (member instanceof GroupRefType) { int id = ((GroupRefType) member).getId().intValue(); GroupType groupType = groups.get(id); if (groupType != null) { groupComponentFields.add(groupType.getNumInGroupId().intValue()); } else { System.err.format("Group missing from repository; id=%d%n", id); } } else if (member instanceof ComponentRefType) { ComponentType componentType = components.get(((ComponentRefType) member).getId().intValue()); getGroupFields(componentType, groupComponentFields); } } } private String getPackage(String... parts) { return String.join(".", parts); } private File getPackagePath(File outputDir, String packageName) { StringBuilder sb = new StringBuilder(); sb.append(packageName.replace('.', File.separatorChar)); return new File(outputDir, sb.toString()); } private String getQualifiedClassName(String packageName, String className) { return String.format("%s.%s", packageName, className); } private String indent(int level) { char[] chars = new char[level * SPACES_PER_LEVEL]; Arrays.fill(chars, ' '); return new String(chars); } private Repository unmarshal(File inputFile) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(Repository.class); Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller(); return (Repository) jaxbUnmarshaller.unmarshal(inputFile); } private void usage() { System.out.format("Usage: java %s ", this.getClass().getName()); } private Writer writeClassDeclaration(Writer writer, String name) throws IOException { writer.write(String.format("%npublic class %s {%n", name)); return writer; } private Writer writeClassDeclaration(Writer writer, String name, String baseClassname) throws IOException { writer.write(String.format("%npublic class %s extends %s {%n", name, baseClassname)); return writer; } private Writer writeComponentAccessors(Writer writer, ComponentRefType member, String packageName) throws IOException { String className = getQualifiedClassName(packageName, member.getName()); writer.write( String.format("%n%spublic void set(%s component) {%n%ssetComponent(component);%n%s}%n", indent(1), className, indent(2), indent(1))); writer.write(String.format( "%n%spublic %s get(%s component) throws FieldNotFound {%n%sgetComponent(component);%n%sreturn component;%n%s}%n", indent(1), className, className, indent(2), indent(2), indent(1))); writer.write(String.format( "%n%spublic %s get%s() throws FieldNotFound {%n%sreturn get(new %s());%n%s}%n", indent(1), className, member.getName(), indent(2), className, indent(1))); return writer; } private Writer writeComponentFieldIds(Writer writer, List componentFields) throws IOException { writer.write(String.format("%Sprivate int[] componentFields = {", indent(1))); for (Integer fieldId : componentFields) { writer.write(String.format("%d, ", fieldId)); } writer.write(String.format("};%n")); writer.write( String.format("%sprotected int[] getFields() { return componentFields; }%n", indent(1))); return writer; } private Writer writeComponentNoArgConstructor(Writer writer, String className) throws IOException { writer.write(String.format("%n%spublic %s() {%n%ssuper();%n%s}%n", indent(1), className, indent(2), indent(1))); return writer; } private Writer writeEndClassDeclaration(Writer writer) throws IOException { writer.write("}\n"); return writer; } private Writer writeFieldAccessors(Writer writer, String name, int id) throws IOException { String qualifiedClassName = getQualifiedClassName(FIELD_PACKAGE, name); writer.write(String.format("%n%spublic void set(%s value) {%n%ssetField(value);%n%s}%n", indent(1), qualifiedClassName, indent(2), indent(1))); writer.write(String.format( "%n%spublic %s get(%s value) throws FieldNotFound {%n%sgetField(value);%n%sreturn value;%n%s}%n", indent(1), qualifiedClassName, qualifiedClassName, indent(2), indent(2), indent(1))); writer.write(String.format( "%n%spublic %s get%s() throws FieldNotFound {%n%sreturn get(new %s());%n%s}%n", indent(1), qualifiedClassName, name, indent(2), qualifiedClassName, indent(1))); writer.write( String.format("%n%spublic boolean isSet(%s field) {%n%sreturn isSetField(field);%n%s}%n", indent(1), qualifiedClassName, indent(2), indent(1))); writer.write(String.format("%n%spublic boolean isSet%s() {%n%sreturn isSetField(%d);%n%s}%n", indent(1), name, indent(2), id, indent(1))); return writer; } private Writer writeFieldArgConstructor(Writer writer, String className, int fieldId, String baseClassname) throws IOException { switch (baseClassname) { case "BooleanField": writer.write(String.format("%n%spublic %s(Boolean data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); writer.write(String.format("%n%spublic %s(boolean data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; case "BytesField": writer.write(String.format("%n%spublic %s(byte[] data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; case "CharField": writer.write(String.format("%n%spublic %s(Character data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); writer.write(String.format("%n%spublic %s(char data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; case "DateField": case "UtcDateOnlyField": writer.write(String.format("%n%spublic %s(Date data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; case "UtcTimeOnlyField": case "UtcTimeStampField": writer.write(String.format("%n%spublic %s(Date data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; case "DecimalField": writer.write(String.format("%n%spublic %s(BigDecimal data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); writer.write(String.format( "%n%spublic %s(double data) {%n%ssuper(%d, BigDecimal.valueOf(data));%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; case "DoubleField": writer.write(String.format("%n%spublic %s(Double data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(2), indent(1))); writer.write(String.format("%n%spublic %s(double data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(2), indent(1))); break; case "IntField": writer.write(String.format("%n%spublic %s(Integer data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); writer.write(String.format("%n%spublic %s(int data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); break; default: writer.write(String.format("%n%spublic %s(String data) {%n%ssuper(%d, data);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); } return writer; } private Writer writeFieldId(Writer writer, int fieldId) throws IOException { writer.write(String.format("%n%spublic static final int FIELD = %d;%n", indent(1), fieldId)); return writer; } private Writer writeFieldNoArgConstructor(Writer writer, String className, int fieldId) throws IOException { writer.write(String.format("%n%spublic %s() {%n%ssuper(%d);%n%s}%n", indent(1), className, indent(2), fieldId, indent(1))); return writer; } private Writer writeFileHeader(Writer writer) throws IOException { writer.write("/* Generated Java Source File */\n"); return writer; } private void writeGroupCreateCase(Writer writer, String parentQualifiedName, GroupType groupType) throws IOException { String numInGroupFieldClassname = getQualifiedClassName(FIELD_PACKAGE, groupType.getNumInGroupName()); writer.write(String.format("%scase %s.FIELD:%n", indent(3), numInGroupFieldClassname)); writer.write(String.format("%sreturn new %s.%s();%n", indent(4), parentQualifiedName, groupType.getNumInGroupName())); List members = groupType.getComponentRefOrGroupRefOrFieldRef(); for (Object member : members) { if (member instanceof GroupRefType) { int id = ((GroupRefType) member).getId().intValue(); GroupType nestedGroupType = groups.get(id); if (groupType != null) { writeGroupCreateCase(writer, String.format("%s.%s", parentQualifiedName, groupType.getNumInGroupName()), nestedGroupType); } else { System.err.format("Group missing from repository; id=%d%n", id); } } } } private Writer writeGroupCreateMethod(Writer writer, List messageList, String messagePackage) throws IOException { writer.write(String.format( "%n%spublic Group create(String beginString, String msgType, int correspondingFieldID) {%n", indent(1))); writer.write(String.format("%sswitch (msgType) {%n", indent(2))); for (MessageType messageType : messageList) { String messageName = messageType.getName(); String scenario = messageType.getScenario(); if (!scenario.equals("base")) { continue; } writer .write(String.format("%scase %s.%s.MSGTYPE:%n", indent(1), messagePackage, messageName)); writer.write(String.format("%sswitch (correspondingFieldID) {%n", indent(2))); List members = messageType.getStructure().getComponentOrComponentRefOrGroup(); for (Object member : members) { if (member instanceof GroupRefType) { int id = ((GroupRefType) member).getId().intValue(); GroupType groupType = groups.get(id); if (groupType != null) { String parentQualifiedName = getQualifiedClassName(messagePackage, messageName); writeGroupCreateCase(writer, parentQualifiedName, groupType); } else { System.err.format("Group missing from repository; id=%d%n", id); } } } writer.write(String.format("%s}%n%sbreak;%n", indent(2), indent(2))); } writer.write(String.format("%s}%n%sreturn null;%n%s}%n", indent(2), indent(2), indent(1))); return writer; } private Writer writeGroupFieldIds(Writer writer, List componentFields) throws IOException { writer.write(String.format("%Sprivate int[] componentGroups = {", indent(1))); for (Integer fieldId : componentFields) { writer.write(String.format("%d, ", fieldId)); } writer.write(String.format("};%n")); writer.write(String.format("%sprotected int[] getGroupFields() { return componentGroups; }%n", indent(1))); return writer; } private void writeGroupInnerClass(FileWriter writer, GroupType groupType, String packageName, String componentPackage) throws IOException { int numInGroupId = groupType.getNumInGroupId().intValue(); String numInGroupFieldName = groupType.getNumInGroupName(); writeStaticClassDeclaration(writer, numInGroupFieldName, "Group"); writeSerializationVersion(writer, SERIALIZATION_VERSION); List groupComponentFields = new ArrayList<>(); getGroupFields(groupType, groupComponentFields); writeOrderFieldIds(writer, groupComponentFields); Integer firstFieldId = groupComponentFields.get(0); writeGroupNoArgConstructor(writer, numInGroupFieldName, numInGroupId, firstFieldId); List members = groupType.getComponentRefOrGroupRefOrFieldRef(); writeMemberAccessors(writer, members, packageName, componentPackage); writeEndClassDeclaration(writer); } private Writer writeGroupNoArgConstructor(Writer writer, String className, int numInGrpId, int firstFieldId) throws IOException { writer.write(String.format("%n%spublic %s() {%n%ssuper(%d, %d, ORDER);%n%s}%n", indent(1), className, indent(2), numInGrpId, firstFieldId, indent(1))); return writer; } private Writer writeImport(Writer writer, String className) throws IOException { writer.write("import "); writer.write(className); writer.write(";\n"); return writer; } private void writeMemberAccessors(FileWriter writer, List members, String packageName, String componentPackage) throws IOException { for (Object member : members) { if (member instanceof FieldRefType) { FieldRefType fieldRefType = (FieldRefType) member; writeFieldAccessors(writer, fieldRefType.getName(), fieldRefType.getId().intValue()); } else if (member instanceof GroupRefType) { writeComponentAccessors(writer, (ComponentRefType) member, componentPackage); int id = ((GroupRefType) member).getId().intValue(); GroupType groupType = groups.get(id); if (groupType != null) { int numInGroupId = groupType.getNumInGroupId().intValue(); String numInGroupName = groupType.getNumInGroupName(); writeFieldAccessors(writer, numInGroupName, numInGroupId); writeGroupInnerClass(writer, groupType, packageName, componentPackage); } else { System.err.format("Group missing from repository; id=%d%n", id); } } else if (member instanceof ComponentRefType) { writeComponentAccessors(writer, (ComponentRefType) member, componentPackage); } } } // In this method, only create messages with base scenario private Writer writeMessageCreateMethod(Writer writer, List messageList, String packageName) throws IOException { writer.write(String.format("%n%spublic Message create(String beginString, String msgType) {%n", indent(1))); writer.write(String.format("%sswitch (msgType) {%n", indent(2))); for (MessageType messageType : messageList) { String name = messageType.getName(); String scenario = messageType.getScenario(); if (!scenario.equals("base")) { continue; } writer.write(String.format("%scase %s.%s.MSGTYPE:%n", indent(2), packageName, name)); writer.write(String.format("%sreturn new %s();%n", indent(3), getQualifiedClassName(packageName, name))); } writer.write(String.format("%s}%n%sreturn new quickfix.fix50sp2.Message();%n%s}%n", indent(2), indent(2), indent(1))); return writer; } private Writer writeMessageDerivedHeaderClass(Writer writer) throws IOException { writeStaticClassDeclaration(writer, "Header", "quickfix.Message.Header"); writeSerializationVersion(writer, SERIALIZATION_VERSION); writer.write(String.format("%n%spublic Header(Message msg) {%n%n%s}%n", indent(1), indent(1))); writeEndClassDeclaration(writer); return writer; } private Writer writeMessageNoArgBaseConstructor(Writer writer, String className) throws IOException { writer.write(String.format("%n%spublic %s() {%n%sthis(null);%n%s}%n", indent(1), className, indent(2), indent(1))); return writer; } private Writer writeMessageNoArgConstructor(Writer writer, String className) throws IOException { writer.write(String.format( "%n%spublic %s() {%n%ssuper();%n%sgetHeader().setField(new quickfix.field.MsgType(MSGTYPE));%n%s}%n", indent(1), className, indent(2), indent(2), indent(1))); return writer; } private Writer writeMsgType(Writer writer, String msgType) throws IOException { writer.write( String.format("%n%spublic static final String MSGTYPE = \"%s\";%n", indent(1), msgType)); return writer; } private Writer writeOrderFieldIds(Writer writer, List componentFields) throws IOException { writer.write(String.format("%Sprivate static final int[] ORDER = {", indent(1))); for (Integer fieldId : componentFields) { writer.write(String.format("%d, ", fieldId)); } writer.write(String.format("0};%n")); return writer; } private Writer writePackage(Writer writer, String packageName) throws IOException { writer.write("package "); writer.write(packageName); writer.write(";\n"); return writer; } private Writer writeProtectedMessageBaseConstructor(Writer writer, String className, String beginString) throws IOException { writer.write(String.format( "%sprotected %s(int[] fieldOrder) {%n%ssuper(fieldOrder);%n%sheader = new Header(this);%n%strailer = new Trailer();%n%sgetHeader().setField(new BeginString(\"%s\"));%n%s}%n", indent(1), className, indent(2), indent(2), indent(2), indent(2), beginString, indent(1))); return writer; } private Writer writeSerializationVersion(Writer writer, long serializationVersion) throws IOException { writer.write(String.format("%sstatic final long serialVersionUID = %dL;%n", indent(1), serializationVersion)); return writer; } private Writer writeStaticClassDeclaration(Writer writer, String name, String baseClassname) throws IOException { writer.write(String.format("%npublic static class %s extends %s {%n", name, baseClassname)); return writer; } private Writer writeValues(Writer writer, CodeSetType codeSet) throws IOException { String type = codeSet.getType(); for (CodeType code : codeSet.getCode()) { switch (type) { case "Boolean": writer.write(String.format("%n%spublic static final boolean %s = %s;%n", indent(1), code.getName(), code.getValue().equals("Y"))); break; case "char": writer.write(String.format("%n%spublic static final char %s = \'%s\';%n", indent(1), code.getName(), code.getValue())); break; case "int": writer.write(String.format("%n%spublic static final int %s = %s;%n", indent(1), code.getName(), code.getValue())); break; default: writer.write(String.format("%n%spublic static final String %s = \"%s\";%n", indent(1), code.getName(), code.getValue())); } } return writer; } // Capitalize first char and any after underscore or space. Leave other caps as-is. private String toTitleCase(String text) { String[] parts = text.split("_ "); return Arrays.stream(parts) .map(part -> part.substring(0, 1).toUpperCase() + part.substring(1)) .collect(Collectors.joining()); } }