/*
* 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 org.f1x.tools;
import org.f1x.api.message.types.ByteEnum;
import org.f1x.api.message.types.IntEnum;
import org.f1x.api.message.types.StringEnum;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
/** Generates enum types from QuickFIX Dictionary. Arguments: C:\projects\toys\6pmfix\resources\quickfix\FIX44.xml C:\projects\toys\6pmfix\src */
public class DictionaryGenerator {
private final StringBuilder textBuffer = new StringBuilder (128);
private final File outputDir;
public DictionaryGenerator(String output) throws IOException {
outputDir = new File (output);
if ( ! outputDir.exists())
throw new IOException("Destination directory doesn't exist: '"+ outputDir.getName() + '\'');
}
private void process (File file) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(file);
Writer constantsFile = generateJavaSource("class", "org.f1x.api.message.fields", "FixTags");
process (doc, constantsFile);
closeJavaSource(constantsFile);
}
private void process(Document doc, Writer constantsFile) throws IOException {
NodeList fieldsList = doc.getElementsByTagName("fields");
assert fieldsList.getLength() == 1;
Node fieldsNode = fieldsList.item(0);
NodeList fieldList = fieldsNode.getChildNodes();
final int cnt = fieldList.getLength();
for (int i = 0; i < cnt; i++) {
Node fieldNode = fieldList.item(i);
if (fieldNode.getNodeType() == Node.ELEMENT_NODE) {
if (fieldNode.getNodeName().equals("field"))
process ((Element)fieldNode, constantsFile);
else
System.err.println("Unexpected element: " + fieldNode.getNodeName());
}
}
}
private void process(Element fieldNode, Writer constantsFile) throws IOException {
String name = fieldNode.getAttribute("name");
String type = fieldNode.getAttribute("type");
int number = Integer.parseInt(fieldNode.getAttribute("number"));
appendFieldDefinition (name, number, constantsFile);
switch (type) {
case "CHAR": generateNumericEnum(name, fieldNode); break;
case "INT": generateNumericEnum(name, fieldNode); break;
case "STRING": generateStringEnum(name, fieldNode); break;
// case "QTY": break;
// case "CURRENCY": break;
// case "AMT": break;
// case "PRICE": break;
// case "EXCHANGE": break;
// case "MULTIPLEVALUESTRING": break;
}
}
private void generateStringEnum(String name, Element fieldNode) throws IOException {
NodeList fieldList = fieldNode.getElementsByTagName("value");
if (fieldList.getLength() > 0) {
generateEnum(name, fieldList, StringEnum.class, "String", "\"", "\"");
}
}
private void generateNumericEnum(String name, Element fieldNode) throws IOException {
NodeList fieldList = fieldNode.getElementsByTagName("value");
if (fieldList.getLength() > 0) {
if (isAllValuesFitInByte(fieldList))
generateEnum(name, fieldList, ByteEnum.class, "byte", "(byte)'", "'");
else
generateEnum(name, fieldList, IntEnum.class, "int", null, null);
}
}
private static boolean isAllValuesFitInByte(NodeList fieldList) {
final int cnt = fieldList.getLength();
try {
for (int i = 0; i < cnt; i++) {
Element valueElem = (Element) fieldList.item(i);
String enumValue = valueElem.getAttribute("enum");
if (enumValue.length() > 1) {
Integer.parseInt(enumValue);
return false;
}
}
return true;
} catch (NumberFormatException e) {
return true; //TODO: what if this is a string enum?
}
}
private void generateEnum(String name, NodeList fieldList, Class enumBaseClass, String enumCodeType, String literalValuePrefix, String literalValueSuffix) throws IOException {
Writer enumWriter = generateJavaSource("enum", "org.f1x.api.message.fields", name, enumBaseClass.getName());
generateValuesList(fieldList, literalValuePrefix, literalValueSuffix, enumWriter);
generateConstructorAndTypeCodeField(name, enumCodeType, enumWriter);
generateValueOf (name, fieldList, literalValuePrefix, literalValueSuffix, enumWriter);
closeJavaSource(enumWriter);
}
private void generateValuesList(NodeList fieldList, String literalValuePrefix, String literalValueSuffix, Writer enumWriter) throws IOException {
final int cnt = fieldList.getLength();
for (int i = 0; i < cnt;) {
Element valueElem = (Element) fieldList.item(i);
String desc = valueElem.getAttribute("description");
String enumValue = valueElem.getAttribute("enum");
textBuffer.setLength(0);
textBuffer.append('\t');
textBuffer.append(desc);
textBuffer.append('(');
if (literalValuePrefix != null)
textBuffer.append(literalValuePrefix);
textBuffer.append(enumValue);
if (literalValueSuffix != null)
textBuffer.append(literalValueSuffix);
textBuffer.append(')');
if (++i < cnt)
textBuffer.append(',');
else
textBuffer.append(';');
textBuffer.append('\n');
enumWriter.write(textBuffer.toString());
}
}
private void generateValueOf(String className, NodeList fieldList, String literalValuePrefix, String literalValueSuffix, Writer enumWriter) throws IOException {
final int cnt = fieldList.getLength();
textBuffer.setLength(0);
textBuffer.append("\n\tpublic static ");
textBuffer.append(className);
textBuffer.append(" parse(String s) {\n");
textBuffer.append("\t\tswitch(s) {\n");
enumWriter.write(textBuffer.toString());
for (int i = 0; i < cnt; i++) {
Element valueElem = (Element) fieldList.item(i);
String desc = valueElem.getAttribute("description");
String enumValue = valueElem.getAttribute("enum");
textBuffer.setLength(0);
textBuffer.append("\t\t\tcase ");
textBuffer.append('"');
textBuffer.append(enumValue);
textBuffer.append("\" : return ");
textBuffer.append(desc);
textBuffer.append(";\n");
enumWriter.write(textBuffer.toString());
}
textBuffer.setLength(0);
textBuffer.append("\t\t}\n\t\treturn null;\n");
textBuffer.append("\t}\n");
enumWriter.write(textBuffer.toString());
}
private void generateConstructorAndTypeCodeField(String name, String enumCodeType, Writer enumWriter) throws IOException {
textBuffer.setLength(0);
textBuffer.append("\n\tprivate final ");
textBuffer.append(enumCodeType);
textBuffer.append(" code;\n\n");
textBuffer.append('\t');
textBuffer.append(name);
textBuffer.append(" (");
textBuffer.append(enumCodeType);
textBuffer.append(" code) {\n");
textBuffer.append ("\t\tthis.code = code;\n");
if (enumCodeType.equals("String")) {
textBuffer.append("\t\tbytes = code.getBytes();\n");
}
textBuffer.append ("\t}\n\n");
textBuffer.append ("\tpublic ");
textBuffer.append (enumCodeType);
textBuffer.append (" getCode() { return code; }\n");
if (enumCodeType.equals("String")) {
textBuffer.append("\n\tprivate final byte[] bytes;");
textBuffer.append("\n\tpublic byte[] getBytes() { return bytes; }\n\n");
}
enumWriter.write(textBuffer.toString());
}
private void appendFieldDefinition(String name, int number, Writer constantsFile) throws IOException {
textBuffer.setLength(0);
textBuffer.append("\tpublic static final int ");
textBuffer.append(name);
textBuffer.append(" = ");
textBuffer.append(number);
textBuffer.append(";\n");
constantsFile.write(textBuffer.toString());
}
private Writer generateJavaSource (String typeName, String packageName, String simpleClassName) throws IOException {
return generateJavaSource (typeName, packageName, simpleClassName, null);
}
private Writer generateJavaSource (String typeName, String packageName, String simpleClassName, String implementsInterface) throws IOException {
File dir = new File (outputDir, packageName.replace('.', File.separatorChar));
if ( ! dir.exists())
if ( ! dir.mkdirs())
throw new IOException("Can't create destination directory \'" + dir.getAbsolutePath() + '\'');
File sourceFile = new File (dir, simpleClassName + ".java");
FileWriter writer = new FileWriter (sourceFile, false);
textBuffer.setLength(0);
textBuffer.append("package ");
textBuffer.append(packageName);
textBuffer.append(";\n\n");
textBuffer.append("// Generated by ");
textBuffer.append(this.getClass().getName());
textBuffer.append(" from QuickFIX dictionary\n");
textBuffer.append("public ");
textBuffer.append(typeName);
textBuffer.append(' ');
textBuffer.append(simpleClassName);
if (implementsInterface != null) {
textBuffer.append(" implements ");
textBuffer.append(implementsInterface);
}
textBuffer.append(" {\n");
writer.write(textBuffer.toString());
return writer;
}
private void closeJavaSource(Writer writer) throws IOException {
textBuffer.setLength(0);
textBuffer.append("\n}");
writer.write(textBuffer.toString());
writer.close();
}
public static void main (String [] args) throws Exception {
// syntax: dictionary.xml output-dir
String dictionary = args[0];
String outputDir = args[1];
DictionaryGenerator gen = new DictionaryGenerator(outputDir);
gen.process(new File (dictionary));
}
}