package quickfix.dictgenerator; import java.io.BufferedWriter; import java.io.File; import java.io.Writer; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; /** * QFJ-483: QFJ Dictionary Generator * Credit for the initial version goes to Morten Kristiansen. * There were some changes made regarding ordering of tags etc, in order to make the * dictionaries look more similar to the existing QFJ dictionaries. * * NB: Do not forget to correct the major/minor number in the session data dictionary afterwards. * * This generator only works with the FPL 2008/09 repository files (http://fixprotocol.org/repository-2008). */ public class Generator { private final Repository repository; private final int major; private final int minor; public Generator(File repositoryDir, int major, int minor ) throws Exception { System.out.println("Building Repository: " + repositoryDir.getAbsolutePath() + "..."); this.repository = new Repository(repositoryDir); this.major = major; this.minor = minor; } public void generate() throws Exception { generateDictionary("session.xml", true, false); generateDictionary("application.xml", false, false); generateDictionary("merged.xml", false, true); // for versions below FIX5.0 } private void generateDictionary(String file, boolean admin, boolean merged ) { File dictionaryFile = new File(file); System.out.println("Creating file: " + dictionaryFile.getAbsolutePath()); StringBuilder builder = new StringBuilder(); if (!merged) { if (admin) { builder.append("<fix type=\"FIXT\" major=\"TODO\" minor=\"TODO\">\n"); } else { builder.append("<fix major=\"" + major + "\" minor=\"" + minor + "\">\n"); } } else { builder.append("<fix major=\"" + major + "\" minor=\"" + minor + "\">\n"); } Map<String, MsgType> msgTypes = new LinkedHashMap<String, MsgType>(); if ( !merged) { if (admin) { msgTypes.putAll( repository.getSessionMsgTypes() ); } else { msgTypes.putAll( repository.getApplicationMsgTypes() ); } } else { msgTypes.putAll( repository.getSessionMsgTypes() ); msgTypes.putAll(repository.getApplicationMsgTypes()); } Set<Integer> fieldsUsed = getAllFieldsUsed(msgTypes); Set<String> componentsUsed = getAllComponentsUsed(msgTypes); // Header if (!merged) { if (admin) { builder.append(" <header>\n"); Component standardHeader = repository.getStandardHeader(msgTypes.values().iterator().next()); addMsgContents(builder, standardHeader.getMsgContent(), " "); builder.append(" </header>\n"); builder.append(" <trailer>\n"); Component standardTrailer = repository.getStandardTrailer(msgTypes.values().iterator().next()); addMsgContents(builder, standardTrailer.getMsgContent(), " "); builder.append(" </trailer>\n"); } else { builder.append(" <header/>\n"); builder.append(" <trailer/>\n"); } } else { builder.append(" <header>\n"); Component standardHeader = repository.getStandardHeader(msgTypes.values().iterator().next()); addMsgContents(builder, standardHeader.getMsgContent(), " "); builder.append(" </header>\n"); builder.append(" <trailer>\n"); Component standardTrailer = repository.getStandardTrailer(msgTypes.values().iterator().next()); addMsgContents(builder, standardTrailer.getMsgContent(), " "); builder.append(" </trailer>\n"); } // Message if (!merged ) { String msgcat = admin ? "admin" : "app"; builder.append(" <messages>\n"); for (MsgType msgType : msgTypes.values()) { if ( msgType.getMsgType().equals("n")) { builder.append(" <!-- \n"); } builder.append(" <message name=\"" + msgType.getName() + "\" msgtype=\"" + msgType.getMsgType() + "\" msgcat=\"" + msgcat + "\">\n"); addMsgContents(builder, msgType.getMsgContent(), " "); builder.append(" </message>\n"); if ( msgType.getMsgType().equals("n")) { builder.append(" --> \n"); } } builder.append(" </messages>\n"); } else { builder.append(" <messages>\n"); for (MsgType msgType : msgTypes.values()) { String msgcat = msgType.getCategory().equals("Session") ? "admin" : "app"; if ( msgType.getMsgType().equals("n")) { builder.append(" <!-- \n"); } builder.append(" <message name=\"" + msgType.getName() + "\" msgtype=\"" + msgType.getMsgType() + "\" msgcat=\"" + msgcat + "\">\n"); addMsgContents(builder, msgType.getMsgContent(), " "); builder.append(" </message>\n"); if ( msgType.getMsgType().equals("n")) { builder.append(" --> \n"); } } builder.append(" </messages>\n"); } // Components builder.append(" <components>\n"); Map<String, Component> components = repository.getComponents(); for (String name : componentsUsed) { Component component = components.get(name); builder.append(" <component name=\"" + name + "\">\n"); Field numInGroup = null; for (Object o : component.getMsgContent()) { if (o instanceof Field && ((Field)o).isNumInGroup()) { numInGroup = (Field) o; break; } } if (numInGroup != null) { builder.append(" <group name=\"" + numInGroup.getFieldName() + "\" required=\"" + (numInGroup.isRequired() ? "Y" : "N") + "\">\n"); addMsgContents(builder, component.getMsgContent(), " "); builder.append(" </group>\n"); } else { addMsgContents(builder, component.getMsgContent(), " "); } builder.append(" </component>\n"); } builder.append(" </components>\n"); // Fields builder.append(" <fields>\n"); Map<String, Field> fields = repository.getFields(); for (Integer tagInt : fieldsUsed) { String tag = String.valueOf(tagInt); Field field = fields.get(tag); String fieldType = field.getType(); builder.append(" <field number=\"" + tag + "\" name=\"" + field.getFieldName() + "\" type=\"" + fieldType.toUpperCase() + "\""); if (!field.getEnums().isEmpty()) { builder.append(">\n"); Set<String> enumDescCache = new HashSet<String>(); for (Enum theEnum : field.getEnums()) { String enumDesc = theEnum.getDesc().toUpperCase(); enumDesc = enumDesc.replaceAll("\\(.*\\)", "" ); // remove stuff in parentheses enumDesc = enumDesc.replaceAll("'","" ); // replace ticks (as in DON'T_KNOW_TRADE) enumDesc = enumDesc.replaceAll("\"","" ); enumDesc = enumDesc.trim(); // trim leading and trailing whitespaces enumDesc = enumDesc.replaceAll("\\W+","_" ); // replace rest of non-word characters by _ char firstChar = enumDesc.charAt(0); if ( Character.isDigit(firstChar) ) { enumDesc = "_" + enumDesc; // make it a valid JAVA identifier } boolean add = enumDescCache.add(enumDesc); if ( add ) { builder.append(" <value enum=\"" + theEnum.getEnumName() + "\" description=\"" + enumDesc + "\"/>\n"); } else { // FIXME ugly workaround to avoid duplicate entries enumDesc = enumDesc + "_1"; enumDescCache.add(enumDesc); builder.append(" <value enum=\"" + theEnum.getEnumName() + "\" description=\"" + enumDesc + "\"/>\n"); } } builder.append(" </field>\n"); } else { builder.append("/>\n"); } } builder.append(" </fields>\n"); builder.append("</fix>"); try { Writer writer = new BufferedWriter(new java.io.FileWriter(dictionaryFile)); writer.write(builder.toString()); writer.flush(); writer.close(); } catch (Exception e) { e.printStackTrace(); } } private void addMsgContents(StringBuilder builder, List<Object> msgContents, String prefix) { for (Object o : msgContents) { if (o instanceof Field) { Field field = (Field) o; if (field.isNumInGroup()) { continue; } builder.append(prefix + "<field name=\"" + field.getFieldName() + "\" required=\"" + (field.isRequired() ? "Y" : "N") + "\"/>\n"); } else if (o instanceof Component) { Component component = (Component) o; if (component.isStandardHeader() || component.isStandardTrailer()) { continue; } builder.append(prefix + "<component name=\"" + component.getName() + "\" required=\"" + (component.isRequired() ? "Y" : "N") + "\"/>\n"); } } } private Set<Integer> getAllFieldsUsed(Map<String, MsgType> msgTypes) { Set<Integer> result = new TreeSet<Integer>(); for (MsgType msgType : msgTypes.values()) { result = addFields(result, msgType.getMsgContent()); } System.out.println("Fields used: " + result); return result; } private Set<Integer> addFields(Set<Integer> result, List<Object> msgContents) { for (Object o : msgContents) { if (o instanceof Field) { result.add(Integer.valueOf(((Field)o).getTag())); } if (o instanceof Component) { result = addFields(result, ((Component)o).getMsgContent()); } } return result; } private Set<String> getAllComponentsUsed(Map<String, MsgType> msgTypes) { Set<String> result = new HashSet<String>(); for (MsgType msgType : msgTypes.values()) { result = addComponents(result, msgType.getMsgContent()); } System.out.println("Components used: " + result); return result; } private Set<String> addComponents(Set<String> result, List<Object> msgContents) { for (Object o : msgContents) { if (o instanceof Component) { Component component = (Component) o; if (!component.isStandardHeader() && !component.isStandardTrailer()) { result.add(((Component)o).getName()); } result = addComponents(result, ((Component)o).getMsgContent()); } } return result; } public static void main(String[] args) throws Exception { if (args == null || args.length < 3) { System.err.println("Usage: Generator [repository path] [major number] [minor number]"); System.exit(1); return; } File repository = new File(args[0]); if (!repository.exists() || !repository.isDirectory()) { System.err.println("Invalid repository: " + repository.getAbsolutePath()); System.exit(1); return; } int major = Integer.valueOf(args[1]); int minor = Integer.valueOf(args[2]); new Generator(repository, major, minor).generate(); } }