From 71b753dc239b624a9fc55a9d643d642f05c3517f Mon Sep 17 00:00:00 2001 From: Christoph John Date: Fri, 30 Oct 2026 13:41:35 +0100 Subject: [PATCH] performance improvement --- .../codegenerator/MessageCodeGenerator.java | 161 +++++++++++------- .../org/quickfixj/codegenerator/Fields.xsl | 4 +- .../org/quickfixj/codegenerator/Message.xsl | 24 +-- .../codegenerator/MessageFactory.xsl | 3 +- .../codegenerator/MessageSubclass.xsl | 29 ++-- 5 files changed, 132 insertions(+), 89 deletions(-) diff --git a/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java b/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java index 4603ef3..1bca375 100644 --- a/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java +++ b/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java @@ -19,6 +19,9 @@ package org.quickfixj.codegenerator; +import net.sf.saxon.TransformerFactoryImpl; +import net.sf.saxon.om.NodeInfo; +import net.sf.saxon.trans.XPathException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -27,10 +30,10 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; @@ -120,7 +123,7 @@ private void generateClassCode(Task task, String className, Map } parameters.put("messagePackage", task.getMessagePackage()); parameters.put("fieldPackage", task.getFieldPackage()); - Document document = getSpecification(task); + NodeInfo document = getSpecificationTree(task); generateCodeFile(task, document, parameters, task.getOutputBaseDirectory() + "/" + task.getMessageDirectory() + "/" + className + ".java", createTransformer(task, className + ".xsl")); @@ -132,36 +135,38 @@ private void generateFieldClasses(Task task) throws ParserConfigurationException + "/"; logInfo(task.getName() + ": generating field classes in " + outputDirectory); writePackageDocumentation(outputDirectory, "FIX field definitions for " + task.getName()); - Document document = getSpecification(task); - List fieldNames = getNames(document.getDocumentElement(), "fields/field"); + List fieldNames = getNames(getSpecification(task).getDocumentElement(), "fields/field"); try { + NodeInfo document = getSpecificationTree(task); Transformer transformer = createTransformer(task, "Fields.xsl"); + // parameters which are the same for all fields are only set once + Map parameters = new HashMap<>(); + parameters.put("fieldPackage", task.getFieldPackage()); + String utcTimestampPrecision = task.getUtcTimestampPrecision(); + if (utcTimestampPrecision != null) { + String utcTimestampPrecisionParameterName = "utcTimestampPrecision"; + if (!UTC_TIMESTAMP_PRECISION_ALLOWED_VALUES.contains(utcTimestampPrecision)) { + throw new CodeGenerationException(new IllegalArgumentException(String.format( + "Allowed values for parameter %s are %s. Supplied value: \"%s\".", + utcTimestampPrecisionParameterName, + String.join(", ", UTC_TIMESTAMP_PRECISION_ALLOWED_VALUES), + utcTimestampPrecision + ))); + } + parameters.put(utcTimestampPrecisionParameterName, utcTimestampPrecision); + } + if (task.isDecimalGenerated()) { + parameters.put("decimalType", "java.math.BigDecimal"); + parameters.put("decimalConverter", "Decimal"); + } + parameters.put(XSLPARAM_SERIAL_UID, SERIAL_UID_STR); + setParameters(transformer, parameters); for (String fieldName : fieldNames) { String outputFile = outputDirectory + fieldName + ".java"; if (!new File(outputFile).exists() || task.isOverwrite()) { // if overwrite is set, then transform and generate the file logDebug("field: " + fieldName); - Map parameters = new HashMap<>(); - parameters.put("fieldName", fieldName); - parameters.put("fieldPackage", task.getFieldPackage()); - String utcTimestampPrecision = task.getUtcTimestampPrecision(); - if (utcTimestampPrecision != null) { - String utcTimestampPrecisionParameterName = "utcTimestampPrecision"; - if (!UTC_TIMESTAMP_PRECISION_ALLOWED_VALUES.contains(utcTimestampPrecision)) { - throw new CodeGenerationException(new IllegalArgumentException(String.format( - "Allowed values for parameter %s are %s. Supplied value: \"%s\".", - utcTimestampPrecisionParameterName, - String.join(", ", UTC_TIMESTAMP_PRECISION_ALLOWED_VALUES), - utcTimestampPrecision - ))); - } - parameters.put(utcTimestampPrecisionParameterName, utcTimestampPrecision); - } - if (task.isDecimalGenerated()) { - parameters.put("decimalType", "java.math.BigDecimal"); - parameters.put("decimalConverter", "Decimal"); - } - parameters.put(XSLPARAM_SERIAL_UID, SERIAL_UID_STR); - generateCodeFile(task, document, parameters, outputFile, transformer); + generateCodeFile(task, document, Collections.singletonMap("fieldName", fieldName), + outputFile, transformer); } } } catch (Exception e) { @@ -176,19 +181,19 @@ private void generateMessageSubclasses(Task task) throws ParserConfigurationExce String outputDirectory = task.getOutputBaseDirectory() + "/" + task.getMessageDirectory() + "/"; writePackageDocumentation(outputDirectory, "Message classes"); - Document document = getSpecification(task); - List messageNames = getNames(document.getDocumentElement(), "messages/message"); + List messageNames = getNames(getSpecification(task).getDocumentElement(), "messages/message"); + NodeInfo document = getSpecificationTree(task); Transformer transformer = createTransformer(task, "MessageSubclass.xsl"); + Map parameters = new HashMap<>(); + parameters.put(XSLPARAM_SERIAL_UID, SERIAL_UID_STR); + parameters.put("orderedFields", Boolean.toString(task.isOrderedFields())); + parameters.put("fieldPackage", task.getFieldPackage()); + parameters.put("messagePackage", task.getMessagePackage()); + setParameters(transformer, parameters); for (String messageName : messageNames) { logDebug("generating message class: " + messageName); - Map parameters = new HashMap<>(); - parameters.put("itemName", messageName); - parameters.put(XSLPARAM_SERIAL_UID, SERIAL_UID_STR); - parameters.put("orderedFields", Boolean.toString(task.isOrderedFields())); - parameters.put("fieldPackage", task.getFieldPackage()); - parameters.put("messagePackage", task.getMessagePackage()); - generateCodeFile(task, document, parameters, outputDirectory + messageName + ".java", - transformer); + generateCodeFile(task, document, Collections.singletonMap("itemName", messageName), + outputDirectory + messageName + ".java", transformer); } } @@ -198,44 +203,73 @@ private void generateComponentClasses(Task task) throws ParserConfigurationExcep logInfo(task.getName() + ": generating component classes"); String outputDirectory = task.getOutputBaseDirectory() + "/" + task.getMessageDirectory() + "/component/"; - Document document = getSpecification(task); - List componentNames = getNames(document.getDocumentElement(), + List componentNames = getNames(getSpecification(task).getDocumentElement(), "components/component"); if (componentNames.size() > 0) { writePackageDocumentation(outputDirectory, "Message component classes"); } + NodeInfo document = getSpecificationTree(task); Transformer transformer = createTransformer(task, "MessageSubclass.xsl"); + Map parameters = new HashMap<>(); + parameters.put("baseClass", "quickfix.MessageComponent"); + parameters.put("subpackage", ".component"); + parameters.put("fieldPackage", task.getFieldPackage()); + parameters.put("messagePackage", task.getMessagePackage()); + parameters.put("orderedFields", Boolean.toString(task.isOrderedFields())); + parameters.put(XSLPARAM_SERIAL_UID, SERIAL_UID_STR); + setParameters(transformer, parameters); for (String componentName : componentNames) { logDebug("generating component class: " + componentName); - Map parameters = new HashMap<>(); - parameters.put("itemName", componentName); - parameters.put("baseClass", "quickfix.MessageComponent"); - parameters.put("subpackage", ".component"); - parameters.put("fieldPackage", task.getFieldPackage()); - parameters.put("messagePackage", task.getMessagePackage()); - parameters.put("orderedFields", Boolean.toString(task.isOrderedFields())); - parameters.put(XSLPARAM_SERIAL_UID, SERIAL_UID_STR); - generateCodeFile(task, document, parameters, outputDirectory + componentName + ".java", - transformer); + generateCodeFile(task, document, Collections.singletonMap("itemName", componentName), + outputDirectory + componentName + ".java", transformer); } } + // A single factory is used so that compiled stylesheets and the specification trees share + // the same Saxon configuration (and name pool). + private final TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl(); + + // Compiled stylesheets are reused for all files and all tasks using the same stylesheet. + private final Map templatesCache = new HashMap<>(); + private Transformer createTransformer(Task task, String xsltFile) throws TransformerFactoryConfigurationError, TransformerConfigurationException { - StreamSource styleSource; File xslt = new File(task.getTransformDirectory() + "/" + xsltFile); - if (xslt.exists()) { - styleSource = new StreamSource(xslt); - } else { - logInfo("Loading predefined xslt file:" + xsltFile); - styleSource = new StreamSource(this.getClass().getResourceAsStream(xsltFile)); + String cacheKey = xslt.exists() ? xslt.getAbsolutePath() : "predefined:" + xsltFile; + Templates templates = templatesCache.get(cacheKey); + if (templates == null) { + StreamSource styleSource; + if (xslt.exists()) { + styleSource = new StreamSource(xslt); + } else { + logInfo("Loading predefined xslt file:" + xsltFile); + styleSource = new StreamSource(this.getClass().getResourceAsStream(xsltFile)); + } + templates = transformerFactory.newTemplates(styleSource); + templatesCache.put(cacheKey, templates); } - TransformerFactory transformerFactory = new net.sf.saxon.TransformerFactoryImpl(); - return transformerFactory.newTransformer(styleSource); + return templates.newTransformer(); } private final Map specificationCache = new HashMap<>(); + private final Map specificationTreeCache = new HashMap<>(); + + /* + * Returns the specification as a Saxon tree. Transforming a DOM would make Saxon wrap or copy + * the DOM for every single generated file, so the DOM is converted only once per task. + */ + private NodeInfo getSpecificationTree(Task task) throws ParserConfigurationException, + SAXException, IOException, XPathException { + NodeInfo tree = specificationTreeCache.get(task.getName()); + if (tree == null) { + tree = transformerFactory.getConfiguration() + .buildDocumentTree(new DOMSource(getSpecification(task))).getRootNode(); + specificationTreeCache.put(task.getName(), tree); + } + return tree; + } + private Document getSpecification(Task task) throws ParserConfigurationException, SAXException, IOException { Document document = specificationCache.get(task.getName()); @@ -286,15 +320,19 @@ private List getNames(Element element, String path, List names) return names; } - private void generateCodeFile(Task task, Document document, Map parameters, - String outputFileName, Transformer transformer) - throws TransformerFactoryConfigurationError, - FileNotFoundException, TransformerException { + private void setParameters(Transformer transformer, Map parameters) { if (parameters != null) { for (Map.Entry entry : parameters.entrySet()) { transformer.setParameter(entry.getKey(), entry.getValue()); } } + } + + private void generateCodeFile(Task task, NodeInfo document, Map parameters, + String outputFileName, Transformer transformer) + throws TransformerFactoryConfigurationError, + FileNotFoundException, TransformerException { + setParameters(transformer, parameters); File outputFile = new File(outputFileName); if (!outputFile.getParentFile().exists()) { @@ -307,12 +345,11 @@ private void generateCodeFile(Task task, Document document, Map } } - DOMSource source = new DOMSource(document); FileOutputStream fos = new FileOutputStream(outputFile); BufferedOutputStream bos = new BufferedOutputStream(fos); try { StreamResult result = new StreamResult(bos); - transformer.transform(source, result); + transformer.transform(document, result); } finally { try { bos.close(); diff --git a/src/main/resources/org/quickfixj/codegenerator/Fields.xsl b/src/main/resources/org/quickfixj/codegenerator/Fields.xsl index d7eeae2..cf291e9 100644 --- a/src/main/resources/org/quickfixj/codegenerator/Fields.xsl +++ b/src/main/resources/org/quickfixj/codegenerator/Fields.xsl @@ -54,10 +54,10 @@ * Contact ask@quickfixengine.org if any conditions of this licensing * are not clear to you. ******************************************************************************/ - + - + package ; import quickfix.Field; diff --git a/src/main/resources/org/quickfixj/codegenerator/Message.xsl b/src/main/resources/org/quickfixj/codegenerator/Message.xsl index 8380396..7dcba45 100644 --- a/src/main/resources/org/quickfixj/codegenerator/Message.xsl +++ b/src/main/resources/org/quickfixj/codegenerator/Message.xsl @@ -20,6 +20,8 @@ + + @@ -126,7 +128,7 @@ public class Message extends quickfix.Message { private static final int[] ORDER = {0}; public () { - super(, , ORDER); + super(, , ORDER); } } @@ -137,7 +139,7 @@ public class Message extends quickfix.Message { - + @@ -148,7 +150,7 @@ public class Message extends quickfix.Message { - @@ -157,15 +159,15 @@ public class Message extends quickfix.Message { - , + , - , + , - @@ -175,11 +177,11 @@ public class Message extends quickfix.Message { - , + , - @@ -212,7 +214,7 @@ public class Message extends quickfix.Message { } public boolean isSet() { - return isSetField(); + return isSetField(); } @@ -235,7 +237,7 @@ public class Message extends quickfix.Message { - @@ -259,7 +261,7 @@ import quickfix.Group; - + import quickfix.Group; diff --git a/src/main/resources/org/quickfixj/codegenerator/MessageFactory.xsl b/src/main/resources/org/quickfixj/codegenerator/MessageFactory.xsl index 50a541e..9a78f45 100644 --- a/src/main/resources/org/quickfixj/codegenerator/MessageFactory.xsl +++ b/src/main/resources/org/quickfixj/codegenerator/MessageFactory.xsl @@ -20,6 +20,7 @@ + @@ -106,7 +107,7 @@ public class MessageFactory implements quickfix.MessageFactory { - + diff --git a/src/main/resources/org/quickfixj/codegenerator/MessageSubclass.xsl b/src/main/resources/org/quickfixj/codegenerator/MessageSubclass.xsl index 85ded0a..a26302e 100644 --- a/src/main/resources/org/quickfixj/codegenerator/MessageSubclass.xsl +++ b/src/main/resources/org/quickfixj/codegenerator/MessageSubclass.xsl @@ -20,6 +20,9 @@ + + + Message @@ -132,7 +135,7 @@ import quickfix.Group; - + @@ -150,7 +153,7 @@ import quickfix.Group; - + @@ -175,7 +178,7 @@ import quickfix.Group; private static final int[] ORDER = {0}; public () { - super(, , ORDER); + super(, , ORDER); } } @@ -186,21 +189,21 @@ import quickfix.Group; - + - + - @@ -209,15 +212,15 @@ import quickfix.Group; - , + , - , + , - @@ -227,11 +230,11 @@ import quickfix.Group; - , + , - @@ -265,7 +268,7 @@ import quickfix.Group; } public boolean isSet() { - return isSetField(); + return isSetField(); } @@ -288,7 +291,7 @@ import quickfix.Group; -