diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index c0d6ad4e2b..42c87a4781 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -26,6 +26,8 @@ jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
+ env:
+ MAVEN_OPTS: "-Xms3g -Xmx6g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500"
strategy:
fail-fast: false
@@ -51,8 +53,9 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ #- name: Autobuild
+ # env:
+ # uses: github/codeql-action/autobuild@v2
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -60,10 +63,8 @@ jobs:
# âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
-
- #- run: |
- # make bootstrap
- # make release
+ - run: |
+ mvn clean package -B -V -e -Pminimal-fix-latest -Dmaven.javadoc.skip=true -Dfindbugs.skip -Dcheckstyle.skip -Dpmd.skip=true -Dspotbugs.skip -Denforcer.skip -DskipTests -Dmaven.test.skip.exec -Dlicense.skip=true -Drat.skip=true -Dspotless.check.skip=true -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120"
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index bef3487adf..602ade349a 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [ubuntu-latest, macOS-latest, windows-latest]
+ os: [ubuntu-latest, macOS-latest]
java: [8, 11, 17]
fail-fast: false
max-parallel: 4
@@ -26,7 +26,36 @@ jobs:
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
+ cache: 'maven'
- name: Test with Maven
env:
- MAVEN_OPTS: "-Xms3g -Xmx3g"
- run: mvn test -B -V -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120"
+ MAVEN_OPTS: "-Xms3g -Xmx5g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500"
+ run: mvn install -B -V -Pminimal-fix-latest -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120"
+
+ test-windows:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [windows-latest]
+ java: [8, 11, 17]
+ fail-fast: false
+ max-parallel: 3
+ name: Test JDK ${{ matrix.java }}, ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Configure pagefile
+ uses: al-cheb/configure-pagefile-action@v1.2
+ with:
+ minimum-size: 8GB
+ maximum-size: 16GB
+ - name: Set up Windows JDK
+ uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin'
+ java-version: ${{ matrix.java }}
+ cache: 'maven'
+ - name: Test with Maven on Windows
+ env:
+ MAVEN_OPTS: "-Xms3g -Xmx5g -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500"
+ run: mvn install -B -V -D"maven.javadoc.skip"="true" -P"skipBundlePlugin,minimal-fix-latest" -D"java.util.logging.config.file"="logging.properties" -D"http.keepAlive"="false" -D"maven.wagon.http.pool"="false" -D"maven.wagon.httpconnectionManager.ttlSeconds"="120"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f8f063ce17..77b6120417 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,6 @@ target/
*.iws
# ignore NetBeans configuration
nb-configuration.xml
+*.bak
+.mvn/
+mvn*
diff --git a/README.md b/README.md
index afd5d7173c..08490c3870 100644
--- a/README.md
+++ b/README.md
@@ -42,17 +42,17 @@ Pull requests are always welcome! Best is if you added a unit test to show that
Fastest: clone the repo and issue the following command.
```
-$ mvn clean package -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin
+$ mvn clean package -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500
```
Slower: if you only want to skip the acceptance test suite:
```
-$ mvn clean package -Dmaven.javadoc.skip=true -DskipAT=true -PskipBundlePlugin
+$ mvn clean package -Dmaven.javadoc.skip=true -DskipAT=true -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500
```
Slow: if you want to run all tests:
```
-$ mvn clean package -Dmaven.javadoc.skip=true -PskipBundlePlugin
+$ mvn clean package -Dmaven.javadoc.skip=true -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500
```
NB: If you want to use the resulting JARs in an OSGi environment you'll have to omit the `-PskipBundlePlugin` option.
@@ -62,7 +62,7 @@ When the project is first created, it will not have the generated message classe
If the IDE reports some errors after the compilation with `mvn clean package`, try to use `mvn clean install`, like:
```
-$ mvn clean install -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin
+$ mvn clean install -Dmaven.javadoc.skip=true -DskipTests -PskipBundlePlugin,minimal-fix-latest -Djdk.xml.xpathExprGrpLimit=500 -Djdk.xml.xpathExprOpLimit=500
```
## configuration options
@@ -283,3 +283,40 @@ void sendOrderCancelRequest() throws SessionNotFound {
Session.sendToTarget(message, "TW", "TARGET");
}
```
+
+## QuickFIX/J Runtime
+
+This project builds artefacts for the standard published FIX specification versions from FIX 4.0 to FIX Latest.
+
+* ```quickfixj-messages-fix40```
+* ```quickfixj-messages-fix41```
+* ```quickfixj-messages-fix42```
+* ```quickfixj-messages-fix43```
+* ```quickfixj-messages-fix44```
+* ```quickfixj-messages-fix50```
+* ```quickfixj-messages-fix50sp1```
+* ```quickfixj-messages-fix50sp2```
+* ```quickfixj-messages-fixlatest```
+* ```quickfixj-messages-fixt11```
+* ```quickfixj-messages-all``` - includes all of the above
+
+These artefacts are **test** dependencies of ```quickfixj-core```. They are **not** specified as _runtime_ dependencies, this makes it easier to customise QuickFIX/J deployments.
+
+If you have no need to customise a FIX integration then you can use the ```org.quickfixj``` artefacts built by this project. Simply include them as dependencies of your application.
+
+Artefacts for unused FIX specification versions can be omitted from your runtime.
+Many integrations will not require ```quickfixj-messages-all``` and need only depend on artefacts for a subset of the FIX standard versions. Please note that FIX Protocol versions 5.0 and later depend on ```quickfixj-messages-fixt11``` which provides the implementation for the FIXT1.1 transport messages.
+
+Many integrations require specialisation of the FIX Messages, Components and/or Fields. This is accomplished by building and using custom artefacts. Please see [Customising QuickFIX/J](./customising-quickfixj.md) for more detail.
+
+### Application Dependencies for QuickFIX/J Messages Build
+
+![image info](./src/main/puml/dependencies_fixt11_fixlatest.png)
+
+![image info](./src/main/puml/dependencies_qfj_all.png)
+
+### Application Dependencies for Custom Messages Build
+
+![image info](./src/main/puml/custom_dependencies.png)
+
+![image info](./src/main/puml/custom_dependencies_fixt11_fixlatest.png)
diff --git a/customising-quickfixj.md b/customising-quickfixj.md
new file mode 100644
index 0000000000..0bab497287
--- /dev/null
+++ b/customising-quickfixj.md
@@ -0,0 +1,94 @@
+
+# Customising QuickFIX/J
+
+The core QuickFIX/J module is agnostic to FIX Protocol Versions. At runtime a QuickFIX/J dictionary with supporting implementation packages is required to use type-safe classes.
+
+The specification for a FIX integration is called a "Rules of Engagement". The Rules of Engagement can be customised with the mutual agreement of the respective counter-parties.
+
+The message, component and field implementations can be provided by a specialised build, along with the corresponding QuickFIX/J dictionary for the custom Rules of Engagement.
+
+The standard distribution of ```quickfixj-core``` can be used with custom artefacts. You need only build artefacts for versions of the Protocol that you use. These can be maintained independently from the QuickFIX/J project, while depending on the QuickFIX/J for the core functionality and tools.
+
+To build custom artefacts it's helpful to understand how QuickFIX/J builds the Field, Component and Message classes from the QuickFIX/J dictionaries and from [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/).
+
+The QuickFIX/J reference implementations for FIX versions FIX4.0 to FIX5.0sp2 and for FIXT1.1 are generated from the QuickFIX dictionaries for the specific version. The dictionaries are located in the ```src/main/resources``` directory of the respective modules of the ```quickfixj-messages``` module.
+Maintaining the FIX4.0 to FIX5.0sp2 builds intentionally provides consistency with the prior QuickFIX/J 2 release in order to ease migration to QuickFIX/J 3.
+
+The most recent standard is defined as [FIX Latest](https://www.fixtrading.org/online-specification/). The QuickFIX/J reference implementation for FIX Latest is generated from a [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) repository file.
+An implementation or customisation of the FIX Standars derived from the FIX Orchestra repository is known as an "_orchestration_".
+The standard FIX Orchestra repository requires some modification to work well with QuickFIX/J.
+This is done by the ```quickfixj-orchestration``` module.
+The ```quickfixj-orchestration``` module publishes a modified Orchestra artefact which can then be the basis of a custom FIX Latest build using QuickFIX/J .
+
+The complete reference FIX Latest specification results in a very large distribution.
+To use FIX Latest, customisation of the [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) repository is advisable.
+Please see [QuickFIX/J Orchestration](./quickfixj-orchestration/readme.md) for details.
+
+## Customisation Scenarios
+
+### **Enable the use of ```BigDecimal``` for FIX Decimal Data Types**
+
+This behaviour is controlled by the ```${generator.decimal}``` build property. It is "false" by default to avoid surprising side effects of incompatible data types.
+
+To enable the use of ```BigDecimal``` in code generation, set the ```${generator.decimal}``` property to "true" in [quickfixj-messages](./quickfixj-messages/readme.md) and build the message artefacts.
+
+```
+
+ true
+
+```
+See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.**
+
+### **Incompatible Data Types**
+
+Some incompatible changes have occurred in the evolution of the FIX protocol. For example see below changes to the type of **OrderQty (38)** :
+
+|FIX Version|Field Name|FIX Datatype|Base Type|QuickFIX/J Datatype|
+|---|---|---|---|---|
+|4.0|OrderQty|int|int|```int```|
+|4.2|OrderQty|Qty|float|```Double``` or ```BigDecimal```|
+
+Only one ```quickfix.Field``` class with the same name may be loaded by the Java classloader so only one version of this Field should be in the classpath. QuickFix/J also verifies the data type using the supplied QuickFIX "Dictionary".
+
+Code generation using ```BigDecimal``` is incompatible at runtime with ```int``` for **OrderQty**. In this case, ```double``` is compatible with ```int``` at run time due to [widening primitive conversion](http://titanium.cs.berkeley.edu/doc/java-langspec-1.0/5.doc.html).
+
+Runtime incompatibilities can be resolved by:
+* Amending the QuickFIX Dictionary to coerce the code generation and/or validation
+* Changing the ordering of code generation and/or overwrite behaviour of code generation
+* Omitting incompatible versions from your customised build
+* Building artefacts independently for the conflicting versions and ensuring they are not used them in the same runtime
+
+See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.**
+
+### **Customising the FIX Protocol for specialised Rules of Engagement**
+
+A Rules of Engagement can include customisation Messages, Components and Fields, including User Defined elements.
+
+It is not necessary to maintain a fork of the entire QuickFIX/J project to provide customised QuickFIX Dictionaries and to
+generate type-safe libraries that are interoperable with QuickFIX/J.
+
+[FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) is intended for customisation to produce machine-readable Rules of Engagement.
+
+Consider creating a new project (or projects) to build the Messages, Components and Fields as needed for your specific Rules of Engagement.
+
+Edit the QuickFIX Dictionary or FIX Orchestra Repository (Orchestration) as required and
+build the Messages, Components and Fields packages using the tools provided by the QuickFIX/J projects.
+
+QuickFIX/J Dictionaries, FIX Orchestra Orchestrations and/or documents can also be generated.
+
+See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.**
+
+### **Managing incompatibility with Prior Versions of QuickFIX/J**
+
+From QuickFIX/J 3.0.0 the code generation for ```quickfix.Field``` prefers the FIX Orchestra Standard. This results in incompatible changes to the names of constants.
+
+For example : ```SettlType.REGULAR_FX_SPOT_SETTLEMENT``` becomes ```SettlType.REGULAR```.
+
+The required code changes may be trivial in most cases, but changes are elective.
+The following describes how to use ```quickfixj-core``` from QuickFIX/J 3 without needing to implement code changes:
+* build the required Message artefacts without the FIX Latest code generation. The Fields will then be generated only from legacy FIX Protocol Versions as they were prior to QuickFIX/J 3.0.0 - **or**
+* if you want to use Messages, Components and/or Fields from FIX Latest while preferring legacy constants,
+manipulate the order of code generation and/or the over-write behaviour of code behaviour to prefer earlier versions of FIX.
+For example, generate FIX Latest first and overwrite the generated Field classes by subsequently running code generation for an earlier version.
+
+See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.**
diff --git a/pom.xml b/pom.xml
index 25afb60fee..924d3793c8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,15 +51,14 @@
http://www.quickfixj.org/jira/
-
- 3.5.0
-
-
quickfixj-codegenerator
quickfixj-dictgenerator
- quickfixj-core
+ quickfixj-class-pruner-maven-plugin
+ quickfixj-orchestration
+ quickfixj-base
quickfixj-messages
+ quickfixj-core
quickfixj-examples
quickfixj-all
quickfixj-distribution
@@ -70,18 +69,20 @@
UTF-8
1.8
2.0.6
- 4.13.2
+ 4.11.0
+ 2.2
+ 5.6.1
8
8
-
+ 3.8.7
3.3.0
3.10.1
3.3.0
- 2.22.2
+ 3.0.0
3.19.0
3.2.1
- 3.4.1
+ 3.5.0
3.4.1
3.4.2
5.1.8
@@ -89,8 +90,214 @@
3.0.0
1.6.13
3.3.0
+ 3.3.4
+ 3.0.0
+ 3.7.1
+ 3.3.0
+ 1.2
+ 8059
+ 1.0.2
+ 1.5.4
+ 1.6.8
+ 1.6.8
+ 2.3.3
+ 2.1.6
+ 2.11.0
+ 32.0.0-jre
+ OrchestraFIXLatest.xml
+ 1.0.2
+ 0.9.1
+ 2.0.0
+
+
+
+ io.fixprotocol.orchestrations
+ fix-standard
+ ${fix-orchestra.standard.version}
+
+
+ io.fixprotocol.orchestra
+ repository
+ ${fix-orchestra.repository.version}
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ ${junit.jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.jupiter.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito-core.version}
+ test
+
+
+ org.hamcrest
+ hamcrest
+ ${hamcrest.version}
+ test
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ ${jaxb.version}
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ runtime
+ ${jaxb.version}
+
+
+ org.apache.mina
+ mina-core
+ ${apache.mina.version}
+
+
+ commons-io
+ commons-io
+ ${commons.io.version}
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${maven.version}
+
+
+ org.apache.maven.shared
+ maven-shared-utils
+ ${maven-shared-utils.version}
+
+
+ org.apache.maven.shared
+ file-management
+ ${file-management.version}
+
+
+ org.apache.maven
+ maven-core
+ ${maven.version}
+
+
+ org.apache.maven
+ maven-artifact
+ ${maven.version}
+ provided
+
+
+ org.apache.maven
+ maven-compat
+ ${maven.version}
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ ${maven-plugin-annotations.version}
+
+
+ org.apache.maven.plugin-testing
+ maven-plugin-testing-harness
+ ${maven-plugin-testing-harness.version}
+
+
+ org.apache.maven
+ maven-project
+ provided
+ 2.2.1
+
+
+ org.codehaus.plexus
+ plexus-utils
+ 3.3.1
+
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ com.sleepycat
+ je
+ 18.3.12
+ true
+
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+ true
+
+
+ com.cloudhopper.proxool
+ proxool
+ ${proxool.version}
+ true
+
+
+
+ avalon-framework
+ avalon-framework-api
+
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ com.cloudhopper.proxool
+ proxool-cglib
+ ${proxool.version}
+ true
+
+
+
+ avalon-framework
+ avalon-framework-api
+
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ jaxen
+ jaxen
+ ${jaxen.version}
+ runtime
+
+
+
+ org.slf4j
+ slf4j-jdk14
+ ${slf4j.version}
+
+
+
+
@@ -110,6 +317,7 @@
${maven-compiler-plugin-version}
true
+ true
2g
4g
@@ -132,6 +340,11 @@
maven-surefire-plugin
${maven-surefire-plugin-version}
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ ${maven-surefire-plugin-version}
+
org.apache.maven.plugins
maven-pmd-plugin
@@ -165,8 +378,9 @@
jar
+ true
none
- 3g
+ 5g
@@ -223,18 +437,113 @@
+
+ org.codehaus.mojo
+ xml-maven-plugin
+ ${xml-maven-plugin-version}
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ ${build-helper-maven-plugin-version}
+
+
+ net.sourceforge.plantuml
+ plantuml
+ ${plantuml-version}
+
+
+ com.github.jeluard
+ plantuml-maven-plugin
+ ${plantuml-maven-plugin-version}
+
+
+ net.sourceforge.plantuml
+ plantuml
+
+
+
+
+ maven-invoker-plugin
+ 3.2.2
+
+
+ maven-plugin-plugin
+ 3.6.0
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+ org.quickfixj.orchestra
+ quickfixj-from-fix-orchestra-code-generator-maven-plugin
+ ${org.quickfixj.orchestra.tools.version}
+
+
+ org.quickfixj.orchestra
+ quickfixj-from-fix-orchestra-dictionary-generator-maven-plugin
+ ${org.quickfixj.orchestra.tools.version}
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.1.0
+
-
org.apache.felix
maven-bundle-plugin
+
+
+ com.github.jeluard
+ plantuml-maven-plugin
+
+
+ ${basedir}
+
+ **/*.puml
+
+
+ true
+
+
+
+ net.sourceforge.plantuml
+ plantuml
+ ${plantuml-version}
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+ enforce-versions
+
+ enforce
+
+
+
+
+ ${maven.version}
+
+
+
+
+
+
-
skipBundlePlugin
@@ -243,15 +552,15 @@
-
- org.apache.felix
- maven-bundle-plugin
-
-
-
-
-
-
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+
+
+
+
@@ -301,7 +610,6 @@
-
ossrh
diff --git a/quickfixj-all/pom.xml b/quickfixj-all/pom.xml
index ac9843cc7b..e07a73f1ab 100644
--- a/quickfixj-all/pom.xml
+++ b/quickfixj-all/pom.xml
@@ -21,22 +21,17 @@
org.quickfixj
- quickfixj-core
- ${project.version}
-
-
- org.quickfixj
- quickfixj-messages-all
+ quickfixj-base
${project.version}
org.quickfixj
- quickfixj-codegenerator
+ quickfixj-core
${project.version}
org.quickfixj
- quickfixj-dictgenerator
+ quickfixj-messages-all
${project.version}
@@ -77,8 +72,7 @@
-
+
com.sleepycat*;resolution:=optional,
org.apache.maven*;resolution:=optional,
diff --git a/quickfixj-base/pom.xml b/quickfixj-base/pom.xml
new file mode 100644
index 0000000000..efd8994e32
--- /dev/null
+++ b/quickfixj-base/pom.xml
@@ -0,0 +1,291 @@
+
+ 4.0.0
+
+ org.quickfixj
+ quickfixj-parent
+ 3.0.0-SNAPSHOT
+
+
+ quickfixj-base
+ jar
+
+ QuickFIX/J Base
+ Base classes for messages and fields
+ http://www.quickfixj.org
+
+
+
+
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.hamcrest
+ hamcrest
+ test
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+
+
+
+
+
+
+
+ ${project.basedir}/src/test/resources
+
+
+ ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources
+
+
+ ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources
+
+
+ ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources
+
+
+ ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ quickfix/fixlatest/**
+
+
+
+
+ org.codehaus.mojo
+ xml-maven-plugin
+
+
+ extractRequiredFields
+ generate-sources
+
+ transform
+
+
+
+
+ ${project.basedir}/../quickfixj-orchestration/target/generated-resources
+ ${project.build.directory}/generated-resources/extracted
+
+ ${orchestra.file}
+
+ ${project.basedir}/src/main/xsl/extractRequiredFields.xsl
+
+
+
+
+
+ addRequiredFields
+ generate-sources
+
+ transform
+
+
+
+
+ ${project.build.directory}/generated-resources/extracted
+ ${project.build.directory}/generated-resources
+
+ ${orchestra.file}
+
+ ${project.basedir}/src/main/xsl/addRequiredFields.xsl
+
+
+
+
+
+
+
+
+ org.quickfixj.orchestra
+ quickfixj-from-fix-orchestra-code-generator-maven-plugin
+
+
+ generate-sources
+
+ codeGeneration
+
+ fixLatest
+
+
+
+ ${project.build.directory}/generated-resources/${orchestra.file}
+ ${project.build.directory}/generated-sources
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ generate-sources
+
+ add-source
+
+
+
+ ${project.build.directory}/generated-sources
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ quickfix/*
+ org/**
+ quickfix/field/*
+ quickfix/field/converter/*
+
+
+ quickfix/fixt11
+ quickfix/fixlatest
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ quickfix/*
+ quickfix/mina/*
+ org/**
+ quickfix/field/*
+ quickfix/field/converter/*
+
+
+ quickfix/fixt11
+ quickfix/fixlatest
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+ true
+ 8
+ src/main/java:${project.build.directory}/generated-sources
+ quickfix.fixt11:quickfix.fixlatest
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ quickfix,quickfix.*,org.quickfixj,org.quickfixj.*
+
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ ${maven-surefire-plugin-version}
+
+
+ true
+
+
+
+ maven-jxr-plugin
+ 2.5
+
+
+
+
+
+
+
+ surefire-java8
+
+ 1.8
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+ surefire
+
+ [1.9,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-base/readme.md b/quickfixj-base/readme.md
new file mode 100644
index 0000000000..a70d37a5b3
--- /dev/null
+++ b/quickfixj-base/readme.md
@@ -0,0 +1,19 @@
+# quickfixj-base
+
+The ```quickfixj-base``` module consists of Java classes on which generated QuickFIX/J Fields, Components and Messages depend.
+
+There is a mutual dependency for a small number of generated Fields used in the ```Standard Header``` and ```Standard Trailer```. These Fields are therefore generated by this module, compiled by it and provided in the jar artefact.
+
+To assure Java runtime compatibility these Fields should not be included in other QuickFIX/J or custom artefacts. The list of fields can be found in the [xslt transform](./src/main/xsl/extractRequiredFields.xsl) used by the build in ```./src/main/xsl/```.
+
+The Fields in question are those defined in the (perhaps counter-intuitive) template match expression.
+
+ Example:
+
+```
+
+
+
+
+
+
+
+
+
+
+ Used when a message is sent via a "hub" or "service bureau". If A sends to Q (the hub) who then sends to B via a separate FIX session, then when Q sends to B the value of this field should represent the SendingTime on the message A sent to Q. (always expressed in UTC (Universal Time Coordinated, also known as "GMT")
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-base/src/main/xsl/extractRequiredFields.xsl b/quickfixj-base/src/main/xsl/extractRequiredFields.xsl
new file mode 100644
index 0000000000..be7aa86e48
--- /dev/null
+++ b/quickfixj-base/src/main/xsl/extractRequiredFields.xsl
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-core/src/test/java/quickfix/BusinessRejectReasonTextTest.java b/quickfixj-base/src/test/java/quickfix/BusinessRejectReasonTextTest.java
similarity index 100%
rename from quickfixj-core/src/test/java/quickfix/BusinessRejectReasonTextTest.java
rename to quickfixj-base/src/test/java/quickfix/BusinessRejectReasonTextTest.java
diff --git a/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java b/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java
new file mode 100644
index 0000000000..edf5f5adc8
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java
@@ -0,0 +1,903 @@
+/*******************************************************************************
+ * Copyright (c) quickfixengine.org All rights reserved.
+ *
+ * This file is part of the QuickFIX FIX Engine
+ *
+ * This file may be distributed under the terms of the quickfixengine.org
+ * license as defined by quickfixengine.org and appearing in the file
+ * LICENSE included in the packaging of this file.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+ * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * See http://www.quickfixengine.org/LICENSE for licensing information.
+ *
+ * Contact ask@quickfixengine.org if any conditions of this licensing
+ * are not clear to you.
+ ******************************************************************************/
+
+package quickfix;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import quickfix.field.MsgType;
+import quickfix.field.NoHops;
+
+public class DataDictionaryTest {
+
+ @Rule
+ public final ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void testDictionary() throws Exception {
+ DataDictionary dd = getDictionary();
+
+ assertEquals("wrong field name", "Currency", dd.getFieldName(15));
+ assertEquals("wrong value description", "BUY", dd.getValueName(4, "B"));
+ assertEquals("wrong value for given value name", "2", dd.getValue(54, "SELL"));
+ assertEquals("wrong value type", FieldType.STRING, dd.getFieldType(1));
+ assertEquals("wrong version", FixVersions.BEGINSTRING_FIX44, dd.getVersion());
+ assertFalse("unexpected field values existence", dd.hasFieldValue(1));
+ assertTrue("unexpected field values nonexistence", dd.hasFieldValue(4));
+ assertFalse("unexpected field existence", dd.isField(9999));
+ assertTrue("unexpected field nonexistence", dd.isField(4));
+ assertTrue("unexpected field value existence", !dd.isFieldValue(4, "C"));
+ assertTrue("unexpected field value nonexistence", dd.isFieldValue(4, "B"));
+ assertTrue("wrong group info", dd.isGroup("A", 384));
+ assertFalse("wrong group info", dd.isGroup("A", 1));
+ assertNotNull("wrong group info", dd.getGroup("6", 232));
+ assertTrue("incorrect header field", dd.isHeaderField(8));
+ assertFalse("incorrect header field", dd.isHeaderField(1));
+ assertTrue("incorrect trailer field", dd.isTrailerField(89));
+ assertFalse("incorrect trailer field", dd.isTrailerField(1));
+ assertTrue("incorrect message field", dd.isMsgField("A", 98));
+ assertFalse("incorrect message field", dd.isMsgField("A", 1));
+ // component field
+ assertTrue("incorrect message field", dd.isMsgField("6", 235));
+ // group->component field
+ //assertTrue("incorrect message field", dd.isMsgField("6", 311));
+ assertTrue("incorrect message type", dd.isMsgType("A"));
+ assertFalse("incorrect message type", dd.isMsgType("%"));
+ assertTrue("incorrect field requirement", dd.isRequiredField("A", 98));
+ assertFalse("incorrect field requirement", dd.isRequiredField("A", 95));
+ assertEquals("incorrect field name", "Account", dd.getFieldName(1));
+ assertEquals("incorrect msg type", "0", dd.getMsgType("Heartbeat"));
+ assertEquals("incorrect msg type", "B", dd.getMsgType("News"));
+ assertFalse(dd.isMsgField("UNKNOWN_TYPE", 1));
+ }
+
+ @Test
+ public void testMissingFieldAttributeForRequired() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ assertConfigErrorForMissingAttributeRequired(data);
+ }
+
+ private void assertConfigErrorForMissingAttributeRequired(String data) {
+ try {
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ } catch (ConfigError e) {
+ // Expected
+ assertTrue(e.getMessage().contains("does not have a 'required'"));
+ }
+ }
+
+ @Test
+ public void testMissingComponentAttributeForRequired() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ assertConfigErrorForMissingAttributeRequired(data);
+ }
+
+ @Test
+ public void testMissingGroupAttributeForRequired() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ assertConfigErrorForMissingAttributeRequired(data);
+ }
+
+ @Test
+ public void testHeaderTrailerRequired() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ DataDictionary dd = new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ assertEquals(1, dd.getNumMessageCategories());
+ assertEquals("0", dd.getMsgType("Heartbeat"));
+
+ assertTrue("BeginString should be required", dd.isRequiredHeaderField(8));
+ assertFalse("OnBehalfOfCompID should not be required", dd.isRequiredHeaderField(115));
+ assertTrue("Checksum should be required", dd.isRequiredTrailerField(10));
+ assertFalse("Signature should not be required", dd.isRequiredTrailerField(89));
+
+ // now tests for fields that aren't actually in the dictionary - should come back false
+ assertFalse("Unknown header field shows up as required", dd.isRequiredHeaderField(666));
+ assertFalse("Unknown trailer field shows up as required", dd.isRequiredTrailerField(666));
+ }
+
+ @Test
+ public void testMessageWithNoChildren40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=msg");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessageWithTextElement40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=msg");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessagesWithNoChildren40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No messages defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessagesWithTextElement40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No messages defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testHeaderWithNoChildren40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=HEADER");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testHeaderWithTextElement40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=HEADER");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testTrailerWithNoChildren40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=TRAILER");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testTrailerWithTextElement40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=TRAILER");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testFieldsWithNoChildren40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testFieldsWithTextElement40() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessageWithNoChildren50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=msg");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessageWithTextElement50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields found: msgType=msg");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessagesWithNoChildren50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No messages defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testMessagesWithTextElement50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No messages defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testHeaderWithNoChildren50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testHeaderWithTextElement50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testTrailerWithNoChildren50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testTrailerWithTextElement50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testFieldsWithNoChildren50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testFieldsWithTextElement50() throws Exception {
+ String data = "";
+ data += "";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+
+ expectedException.expect(ConfigError.class);
+ expectedException.expectMessage("No fields defined");
+
+ new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ }
+
+ @Test
+ public void testHeaderGroupField() throws Exception {
+ DataDictionary dd = getDictionary();
+ assertTrue(dd.isHeaderGroup(NoHops.FIELD));
+ }
+
+ // QF C++ treats the string argument as a filename although it's
+ // named 'url'. QFJ string argument can be either but this test
+ // ensures the DD works correctly with a regular file path.
+ @Test
+ public void testDictionaryWithFilename() throws Exception {
+ DataDictionary dd = new DataDictionary("FIX40.xml");
+ assertEquals("wrong field name", "Currency", dd.getFieldName(15));
+ // It worked!
+ }
+
+ // Support finding DD in classpath
+ @Test
+ public void testDictionaryInClassPath() throws Exception {
+ URLClassLoader customClassLoader = new URLClassLoader(new URL[] { new URL("file:etc") },
+ getClass().getClassLoader());
+ Thread currentThread = Thread.currentThread();
+ ClassLoader previousContextClassLoader = currentThread.getContextClassLoader();
+ currentThread.setContextClassLoader(customClassLoader);
+ try {
+ DataDictionary dd = new DataDictionary("FIX40.xml");
+ assertEquals("wrong field name", "Currency", dd.getFieldName(15));
+ // It worked!
+ } finally {
+ currentThread.setContextClassLoader(previousContextClassLoader);
+ }
+ }
+
+ // QFJ-235
+ @Test
+ public void testWildcardEnumValue() throws Exception {
+ DataDictionary dd = getDictionary();
+ assertTrue(dd.isFieldValue(65, "FOO"));
+ }
+
+ @Test
+ public void testMessageCategory() throws Exception {
+ DataDictionary dd = getDictionary();
+ assertTrue(dd.isAdminMessage(MsgType.LOGON));
+ assertFalse(dd.isAppMessage(MsgType.LOGON));
+ assertFalse(dd.isAdminMessage(MsgType.NEW_ORDER_SINGLE));
+ assertTrue(dd.isAppMessage(MsgType.NEW_ORDER_SINGLE));
+ }
+
+ // QFJ-535
+ @Test
+ public void testValidateFieldsOutOfOrderForGroups() throws Exception {
+ final DataDictionary dictionary = new DataDictionary(getDictionary());
+ dictionary.setCheckUnorderedGroupFields(false);
+ Message messageWithGroupLevel1 = new Message(
+ "8=FIX.4.4\0019=185\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" +
+ "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" +
+ "54=1\00155=QFJ\00159=0\00178=1\00179=allocAccount\001736=currency\001661=1\00110=130\001",
+ dictionary);
+ dictionary.validate(messageWithGroupLevel1);
+
+ Message messageWithGroupLevel2 = new Message(
+ "8=FIX.4.4\0019=185\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" +
+ "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" +
+ "54=1\00155=QFJ\00159=0\00178=1\00179=allocAccount\001539=1\001524=1\001538=1\001525=a\00110=145\001",
+ dictionary);
+ dictionary.validate(messageWithGroupLevel2);
+ }
+
+ @Test
+ public void shouldLoadDictionaryWhenExternalDTDisEnabled() throws ConfigError {
+ new DataDictionary("FIX_External_DTD.xml", DocumentBuilderFactory::newInstance);
+ }
+
+ @Test
+ public void shouldFailToLoadDictionaryWhenExternalDTDisDisabled() {
+ try {
+ new DataDictionary("FIX_External_DTD.xml");
+ fail("should fail to load dictionary with external DTD");
+ } catch (ConfigError e) {
+ assertEquals("External DTD: Failed to read external DTD 'mathml.dtd', because 'http' access is not allowed due to restriction set by the accessExternalDTD property.", e.getCause().getCause().getMessage());
+ }
+ }
+
+ /**
+ * For FIX.Latest a minor version is not required.
+ */
+ @Test
+ public void testMissingMinorVersion() throws Exception {
+ String data = "";
+ data += "";
+ data = getCommonDataDictionaryString(data);
+
+ DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ assertEquals(0, dataDictionary.getMinorVersion());
+ }
+
+ @Test
+ public void testFixLatestMajorVersion() throws Exception {
+ String data = "";
+ data += "";
+ data = getCommonDataDictionaryString(data);
+
+ DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ assertEquals(0, dataDictionary.getMinorVersion());
+ assertEquals("FIX.Latest", dataDictionary.getFullVersion());
+ }
+
+ @Test
+ public void testFixLatestMajorVersionAndEP() throws Exception {
+ String data = "";
+ data += "";
+ data = getCommonDataDictionaryString(data);
+
+ DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ assertEquals(0, dataDictionary.getMinorVersion());
+ assertEquals("FIX.Latest_EP260", dataDictionary.getFullVersion());
+ }
+
+ @Test
+ public void testSP() throws Exception {
+ String data = "";
+ data += "";
+ data = getCommonDataDictionaryString(data);
+
+ DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ assertEquals(0, dataDictionary.getMinorVersion());
+ assertEquals("FIX.5.0", dataDictionary.getVersion());
+ assertEquals("FIX.5.0SP2", dataDictionary.getFullVersion());
+ }
+
+ @Test
+ public void testEPAndSP() throws Exception {
+ String data = "";
+ data += "";
+ data = getCommonDataDictionaryString(data);
+
+ DataDictionary dataDictionary = new DataDictionary(new ByteArrayInputStream(data.getBytes()));
+ assertEquals(0, dataDictionary.getMinorVersion());
+ assertEquals("FIX.5.0", dataDictionary.getVersion());
+ assertEquals("FIX.5.0SP2_EP260", dataDictionary.getFullVersion());
+ }
+
+ private String getCommonDataDictionaryString(String data) {
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ data += " ";
+ return data;
+ }
+
+
+ //
+ // Group Validation Tests in RepeatingGroupTest
+ //
+
+ private static DataDictionary testDataDictionary;
+
+ /**
+ * Returns a singleton FIX 4.4 data dictionary.
+ * NOTE: the returned dictionary must not be modified in any way
+ * (e.g. by calling any of its setter methods). If it needs to
+ * be modified, it can be cloned by using the
+ * {@link DataDictionary#DataDictionary(DataDictionary)
+ * DataDictionary copy constructor}.
+ *
+ * @return a singleton FIX 4.4 data dictionary
+ * @throws Exception if the data dictionary cannot be loaded
+ */
+ public static DataDictionary getDictionary() throws Exception {
+ if (testDataDictionary == null) {
+ testDataDictionary = getDictionary("FIX44.xml");
+ }
+ return testDataDictionary;
+ }
+
+ /**
+ * Loads and returns the named data dictionary.
+ *
+ * @param fileName the data dictionary file name (e.g. "FIX44.xml")
+ * @return a new data dictionary instance
+ * @throws Exception if the named data dictionary cannot be loaded
+ */
+ public static DataDictionary getDictionary(String fileName) throws Exception {
+ return new DataDictionary(DataDictionaryTest.class.getClassLoader()
+ .getResourceAsStream(fileName));
+ }
+
+}
diff --git a/quickfixj-core/src/test/java/quickfix/DayConverterTest.java b/quickfixj-base/src/test/java/quickfix/DayConverterTest.java
similarity index 100%
rename from quickfixj-core/src/test/java/quickfix/DayConverterTest.java
rename to quickfixj-base/src/test/java/quickfix/DayConverterTest.java
diff --git a/quickfixj-core/src/test/java/quickfix/DictionaryTest.java b/quickfixj-base/src/test/java/quickfix/DictionaryTest.java
similarity index 100%
rename from quickfixj-core/src/test/java/quickfix/DictionaryTest.java
rename to quickfixj-base/src/test/java/quickfix/DictionaryTest.java
diff --git a/quickfixj-base/src/test/java/quickfix/ExceptionTest.java b/quickfixj-base/src/test/java/quickfix/ExceptionTest.java
new file mode 100644
index 0000000000..e87eb0adf1
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/ExceptionTest.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright (c) quickfixengine.org All rights reserved.
+ *
+ * This file is part of the QuickFIX FIX Engine
+ *
+ * This file may be distributed under the terms of the quickfixengine.org
+ * license as defined by quickfixengine.org and appearing in the file
+ * LICENSE included in the packaging of this file.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+ * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * See http://www.quickfixengine.org/LICENSE for licensing information.
+ *
+ * Contact ask@quickfixengine.org if any conditions of this licensing
+ * are not clear to you.
+ ******************************************************************************/
+
+package quickfix;
+
+import junit.framework.TestCase;
+
+public class ExceptionTest extends TestCase {
+
+ public void testDoNotSend() {
+ new DoNotSend();
+ }
+
+ public void testIncorrectDataFormat() {
+ IncorrectDataFormat e = new IncorrectDataFormat(5, "test");
+ assertEquals(5, e.getField());
+ assertEquals("test", e.getData());
+ }
+
+ public void testIncorrectTagValue() {
+ new IncorrectTagValue(5);
+ IncorrectTagValue e = new IncorrectTagValue(5, "test");
+ }
+
+ public void testRuntimeError() {
+ new RuntimeError();
+ new RuntimeError("test");
+ new RuntimeError(new Exception());
+ }
+}
diff --git a/quickfixj-core/src/test/java/quickfix/FieldConvertersTest.java b/quickfixj-base/src/test/java/quickfix/FieldConvertersTest.java
similarity index 100%
rename from quickfixj-core/src/test/java/quickfix/FieldConvertersTest.java
rename to quickfixj-base/src/test/java/quickfix/FieldConvertersTest.java
diff --git a/quickfixj-base/src/test/java/quickfix/FieldMapTest.java b/quickfixj-base/src/test/java/quickfix/FieldMapTest.java
new file mode 100644
index 0000000000..30490b9adc
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/FieldMapTest.java
@@ -0,0 +1,83 @@
+package quickfix;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Iterator;
+import java.util.Optional;
+
+import org.junit.Test;
+
+/**
+ * Tests the {@link FieldMap} class.
+ * Specifically, verifies that the setters for {@link UtcTimeStampField} work correctly.
+ *
+ * @author toli
+ */
+public class FieldMapTest {
+
+ private void testOrdering(int[] vals, int[] order, int[] expected) {
+ FieldMap map = new Message(order);
+ for (int v : vals)
+ map.setInt(v, v);
+ Iterator> it = map.iterator();
+ for (int e : expected)
+ assertEquals(String.valueOf(e), it.next().getObject());
+ }
+
+ @Test
+ public void testOrdering() {
+ testOrdering(new int[]{1, 2, 3}, null, new int[]{1, 2, 3});
+ testOrdering(new int[]{3, 2, 1}, null, new int[]{1, 2, 3});
+ testOrdering(new int[]{1, 2, 3}, new int[]{1, 2, 3}, new int[]{1, 2, 3});
+ testOrdering(new int[]{3, 2, 1}, new int[]{1, 2, 3}, new int[]{1, 2, 3});
+ testOrdering(new int[]{1, 2, 3}, new int[]{1, 3, 2}, new int[]{1, 3, 2});
+ testOrdering(new int[]{3, 2, 1}, new int[]{1, 3, 2}, new int[]{1, 3, 2});
+ testOrdering(new int[]{1, 2, 3}, new int[]{1, 3}, new int[]{1, 3, 2});
+ testOrdering(new int[]{3, 2, 1}, new int[]{1, 3}, new int[]{1, 3, 2});
+ testOrdering(new int[]{1, 2, 3}, new int[]{3, 1}, new int[]{3, 1, 2});
+ testOrdering(new int[]{3, 2, 1}, new int[]{3, 1}, new int[]{3, 1, 2});
+ }
+
+ @Test
+ public void testOptionalString() {
+ FieldMap map = new Message();
+ map.setField(new StringField(128, "bigbank"));
+ Optional optValue = map.getOptionalString(128);
+ assertTrue(optValue.isPresent());
+ assertEquals("bigbank", optValue.get());
+ assertFalse(map.getOptionalString(129).isPresent());
+ }
+
+ @Test
+ public void testOptionalDecimal() {
+ FieldMap map = new Message();
+ map.setField(new DecimalField(44, new BigDecimal("1565.10")));
+ Optional optValue = map.getOptionalDecimal(44);
+ assertTrue(optValue.isPresent());
+ assertEquals(0, optValue.get().compareTo(new BigDecimal("1565.10")));
+ assertFalse(map.getOptionalDecimal(6).isPresent());
+ }
+
+ @Test
+ public void testNullFieldException() {
+ FieldMap map = new Message();
+ StringField field = new StringField(0, null);
+ assertThrows(FieldException.class, () -> map.setField(field));
+ }
+
+ @Test
+ public void testRemoveGroup() {
+ FieldMap map = new Message();
+ Group group = new Group(73, 11);
+ map.addGroup(group);
+ assertTrue(map.hasGroup(73));
+ map.removeGroup(73);
+ assertFalse(map.hasGroup(73));
+ }
+}
diff --git a/quickfixj-base/src/test/java/quickfix/FieldTest.java b/quickfixj-base/src/test/java/quickfix/FieldTest.java
new file mode 100644
index 0000000000..a42f7c560d
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/FieldTest.java
@@ -0,0 +1,234 @@
+/*******************************************************************************
+ * Copyright (c) quickfixengine.org All rights reserved.
+ *
+ * This file is part of the QuickFIX FIX Engine
+ *
+ * This file may be distributed under the terms of the quickfixengine.org
+ * license as defined by quickfixengine.org and appearing in the file
+ * LICENSE included in the packaging of this file.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+ * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * See http://www.quickfixengine.org/LICENSE for licensing information.
+ *
+ * Contact ask@quickfixengine.org if any conditions of this licensing
+ * are not clear to you.
+ ******************************************************************************/
+
+package quickfix;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Date;
+
+import org.junit.Test;
+import org.quickfixj.CharsetSupport;
+
+public class FieldTest {
+
+ private void testFieldCalcuations(String value, int checksum, int length) {
+ Field field = new Field<>(12, value);
+ field.setObject(value);
+ assertEquals("12=" + value, field.toString());
+ assertEquals(checksum, field.getChecksum());
+ assertEquals(length, field.getLength());
+
+ value = value.substring(0, value.length() - 1) + (char)(value.charAt(value.length() - 1) + 1);
+ checksum = (checksum + 1) & 0xFF;
+ field.setObject(value);
+ assertEquals("12=" + value, field.toString());
+ assertEquals(checksum, field.getChecksum());
+ assertEquals(length, field.getLength());
+
+ field.setTag(13);
+ checksum = (checksum + 1) & 0xFF;
+ assertEquals("13=" + value, field.toString());
+ assertEquals(checksum, field.getChecksum());
+ assertEquals(length, field.getLength());
+ }
+
+ @Test
+ public void testFieldCalculationsWithDefaultCharset() {
+ testFieldCalcuations("VALUE", 30, 9);
+ }
+
+ @Test
+ public void testFieldCalculationsWithUTF8Charset() throws UnsupportedEncodingException {
+ CharsetSupport.setCharset("UTF-8");
+ try {
+ testFieldCalcuations("\u6D4B\u9A8C\u6570\u636E", 50, 16);
+ } finally {
+ CharsetSupport.setCharset(CharsetSupport.getDefaultCharset());
+ }
+ }
+
+ @Test
+ public void testDateField() {
+ DateField field = new DateField(11);
+ Date date = new Date();
+ field.setValue(date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ field = new DateField(11, date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ }
+
+ @Test
+ public void testUtcDateOnlyField() {
+ UtcDateOnlyField field = new UtcDateOnlyField(11);
+ LocalDate date = LocalDate.now();
+ field.setValue(date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ field = new UtcDateOnlyField(11, date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ }
+
+ @Test
+ public void testUtcTimeOnlyField() {
+ UtcTimeOnlyField field = new UtcTimeOnlyField(11);
+ LocalTime date = LocalTime.now();
+ field.setValue(date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ field = new UtcTimeOnlyField(11, date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ }
+
+ @Test
+ public void testUtcTimeStampField() {
+ UtcTimeStampField field = new UtcTimeStampField(11);
+ LocalDateTime date = LocalDateTime.now();
+ field.setValue(date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ field = new UtcTimeStampField(11, date);
+ assertEquals(11, field.getTag());
+ assertEquals(date, field.getValue());
+ }
+
+ @Test
+ public void testBooleanField() {
+ BooleanField field = new BooleanField(11);
+ field.setValue(true);
+ assertEquals(11, field.getTag());
+ assertTrue(field.getValue());
+ field.setValue(Boolean.FALSE);
+ assertEquals(11, field.getTag());
+ assertFalse(field.getValue());
+ field = new BooleanField(22, true);
+ assertEquals(22, field.getTag());
+ assertTrue(field.getValue());
+ field = new BooleanField(33, Boolean.TRUE);
+ assertEquals(33, field.getTag());
+ assertTrue(field.getValue());
+ }
+
+ @Test
+ public void testDoubleField() {
+ DoubleField field = new DoubleField(11);
+ field.setValue(12.3);
+ assertEquals(11, field.getTag());
+ assertEquals(12.3, field.getValue(), 0);
+ field.setValue(new Double(23.4));
+ assertEquals(11, field.getTag());
+ assertEquals(23.4, field.getValue(), 0);
+ field = new DoubleField(22, 34.5);
+ assertEquals(22, field.getTag());
+ assertEquals(34.5, field.getValue(), 0);
+ field = new DoubleField(33, new Double(45.6));
+ assertEquals(33, field.getTag());
+ assertEquals(45.6, field.getValue(), 0);
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testDoubleFieldException() {
+ DoubleField field = new DoubleField(11, Double.NaN);
+ }
+
+ @Test
+ public void testDecimalField() {
+ DecimalField field = new DecimalField(11);
+ field.setValue(12.3);
+ assertEquals(11, field.getTag());
+ assertEquals(BigDecimal.valueOf(12.3), field.getValue());
+ field.setValue(23.4);
+ assertEquals(11, field.getTag());
+ assertEquals(BigDecimal.valueOf(23.4), field.getValue());
+ field = new DecimalField(22, 34.5);
+ assertEquals(22, field.getTag());
+ assertEquals(BigDecimal.valueOf(34.5), field.getValue());
+ field = new DecimalField(33, 45.6);
+ assertEquals(33, field.getTag());
+ assertEquals(BigDecimal.valueOf(45.6), field.getValue());
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void testDecimalFieldException() {
+ DecimalField field = new DecimalField(11, Double.POSITIVE_INFINITY);
+ }
+
+ @Test
+ public void testCharField() {
+ CharField field = new CharField(11);
+ field.setValue('x');
+ assertEquals(11, field.getTag());
+ assertEquals('x', field.getValue());
+ field.setValue(Character.valueOf('X'));
+ assertEquals(11, field.getTag());
+ assertEquals('X', field.getValue());
+ field = new CharField(22, 'a');
+ assertEquals(22, field.getTag());
+ assertEquals('a', field.getValue());
+ field = new CharField(33, Character.valueOf('A'));
+ assertEquals(33, field.getTag());
+ assertEquals('A', field.getValue());
+ }
+
+ @Test
+ public void testIntField() {
+ IntField field = new IntField(11);
+ field.setValue(12);
+ assertEquals(11, field.getTag());
+ assertEquals(12, field.getValue());
+ field.setValue(Integer.valueOf(23));
+ assertEquals(11, field.getTag());
+ assertEquals(23, field.getValue());
+ field = new IntField(22, 23);
+ assertEquals(22, field.getTag());
+ assertEquals(23, field.getValue());
+ field = new IntField(33, Integer.valueOf(44));
+ assertEquals(33, field.getTag());
+ assertEquals(44, field.getValue());
+ }
+
+ @Test
+ public void testFieldhashCode() throws Exception {
+ assertEqualsAndHash(new IntField(11, 100), new IntField(11, 100));
+ assertEqualsAndHash(new DoubleField(11, 100.0), new DoubleField(11, 100.0));
+ assertEqualsAndHash(new StringField(11, "foo"), new StringField(11, "foo"));
+ assertEqualsAndHash(new BooleanField(11, true), new BooleanField(11, true));
+ assertEqualsAndHash(new CharField(11, 'x'), new CharField(11, 'x'));
+ LocalDateTime date = LocalDateTime.now();
+ assertEqualsAndHash(new UtcDateOnlyField(11, date.toLocalDate()), new UtcDateOnlyField(11, date.toLocalDate()));
+ assertEqualsAndHash(new UtcTimeOnlyField(11, date.toLocalTime()), new UtcTimeOnlyField(11, date.toLocalTime()));
+ assertEqualsAndHash(new UtcTimeStampField(11, date), new UtcTimeStampField(11, date));
+ }
+
+ private void assertEqualsAndHash(Field> field1, Field> field2) {
+ assertEquals("fields not equal", field1, field2);
+ assertEquals("fields hashcode not equal", field1.hashCode(), field2.hashCode());
+ }
+}
diff --git a/quickfixj-core/src/test/java/quickfix/FileUtilTest.java b/quickfixj-base/src/test/java/quickfix/FileUtilTest.java
similarity index 97%
rename from quickfixj-core/src/test/java/quickfix/FileUtilTest.java
rename to quickfixj-base/src/test/java/quickfix/FileUtilTest.java
index c50b4a21a4..56b10e29ce 100644
--- a/quickfixj-core/src/test/java/quickfix/FileUtilTest.java
+++ b/quickfixj-base/src/test/java/quickfix/FileUtilTest.java
@@ -43,7 +43,7 @@ public void testFileLocation() throws Exception {
@Test
public void testClassResourceLocation() throws Exception {
- InputStream in = FileUtil.open(Message.class, "Session.class");
+ InputStream in = FileUtil.open(Message.class, "FixVersions.class");
in.close();
assertNotNull("Resource not found", in);
}
diff --git a/quickfixj-base/src/test/java/quickfix/MessageTest.java b/quickfixj-base/src/test/java/quickfix/MessageTest.java
new file mode 100644
index 0000000000..91432d48a5
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/MessageTest.java
@@ -0,0 +1,741 @@
+package quickfix;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import quickfix.field.ApplExtID;
+import quickfix.field.ApplVerID;
+import quickfix.field.BeginString;
+import quickfix.field.BodyLength;
+import quickfix.field.CheckSum;
+import quickfix.field.CstmApplVerID;
+import quickfix.field.MsgSeqNum;
+import quickfix.field.MsgType;
+import quickfix.field.SecureData;
+import quickfix.field.SenderCompID;
+import quickfix.field.SendingTime;
+import quickfix.field.SessionRejectReason;
+import quickfix.field.Signature;
+import quickfix.field.SignatureLength;
+import quickfix.field.TargetCompID;
+import quickfix.field.TargetSubID;
+
+public class MessageTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+
+ @Test
+ public void testRepeatingField() throws Exception {
+ final Message m = new Message(
+ "8=FIX.4.0\0019=100\00135=D\00134=2\00149=TW\00156=ISLD\00111=ID\00121=1\001"
+ + "40=1\00154=1\00140=2\00138=200\00155=INTC\00110=160\001");
+ assertFalse("message should be invalid", m.hasValidStructure());
+ assertEquals("wrong invalid tag", 40, m.getInvalidTag());
+ }
+
+ @Test
+ public void testHeaderCustomFieldOrdering() throws Exception {
+
+ class MyMessage extends Message {
+
+ final int[] headerFieldOrder = {
+ BeginString.FIELD,
+ BodyLength.FIELD,
+ MsgType.FIELD,
+ TargetSubID.FIELD,
+ SendingTime.FIELD,
+ MsgSeqNum.FIELD,
+ SenderCompID.FIELD,
+ TargetCompID.FIELD
+ };
+
+ public MyMessage() {
+ super();
+ header = new Header(headerFieldOrder);
+ }
+ }
+
+ final MyMessage myMessage = new MyMessage();
+
+ myMessage.getHeader().setField(new SenderCompID("foo"));
+ myMessage.getHeader().setField(new MsgSeqNum(22));
+ myMessage.getHeader().setString(SendingTime.FIELD, "20120922-11:00:00");
+ myMessage.getHeader().setField(new TargetCompID("bar"));
+
+ assertTrue(myMessage.toString().contains("52=20120922-11:00:00\00134=22\00149=foo\00156=bar"));
+ }
+
+ @Test
+ public void testHeaderFieldWithCustomTransportDictionaryConstructorReadsHeaderField() throws Exception {
+
+ final DataDictionary customSessionDictionary = new DataDictionary("FIXT11_Custom_Test.xml");
+ customSessionDictionary.setAllowUnknownMessageFields(false);
+
+ final DataDictionary standardSessionDictionary = new DataDictionary("FIXT11.xml");
+ standardSessionDictionary.setAllowUnknownMessageFields(false);
+
+ final DataDictionary applicationDictionary = new DataDictionary("FIX50.xml");
+ applicationDictionary.setAllowUnknownMessageFields(false);
+
+ final String sep = "\001";
+ final StringBuilder sb = new StringBuilder();
+ sb.append("8=FIXT1.1");
+ sb.append(sep);
+ sb.append("9=112");
+ sb.append(sep);
+ sb.append("35=6");
+ sb.append(sep);
+ sb.append("49=SENDER_COMP_ID");
+ sb.append(sep);
+ sb.append("56=TARGET_COMP_ID");
+ sb.append(sep);
+ sb.append("34=20");
+ sb.append(sep);
+ sb.append("52=20120922-11:00:00");
+ sb.append(sep);
+ sb.append("12312=foo");
+ sb.append(sep);
+ sb.append("23=123456");
+ sb.append(sep);
+ sb.append("28=N");
+ sb.append(sep);
+ sb.append("55=[N/A]");
+ sb.append(sep);
+ sb.append("54=1");
+ sb.append(sep);
+ sb.append("27=U");
+ sb.append(sep);
+ sb.append("10=52");
+ sb.append(sep);
+ final String messageData = sb.toString();
+
+ final Message standardMessage = new Message(messageData, standardSessionDictionary, applicationDictionary, true);
+
+ // Test that field is in body not the header
+ assertTrue(standardMessage.toString().contains("12312=foo"));
+ assertFalse(standardMessage.getHeader().isSetField(12312));
+ assertTrue(standardMessage.isSetField(12312));
+ assertEquals("foo", standardMessage.getString(12312));
+
+ // Test that field is correctly classified in header with customSessionDictionary
+ final Message customMessage = new Message(messageData, customSessionDictionary, applicationDictionary, true);
+ assertTrue(customMessage.toString().contains("12312=foo"));
+ assertTrue(customMessage.getHeader().isSetField(12312));
+ assertEquals("foo", customMessage.getHeader().getString(12312));
+ assertFalse(customMessage.isSetField(12312));
+ }
+
+ @Test
+ public void testTrailerCustomFieldOrdering() throws Exception {
+
+ class MyMessage extends Message {
+
+ final int[] trailerFieldOrder = {Signature.FIELD, SignatureLength.FIELD, CheckSum.FIELD};
+
+ public MyMessage() {
+ super();
+ trailer = new Trailer(trailerFieldOrder);
+ }
+ }
+
+ final MyMessage myMessage = new MyMessage();
+
+ myMessage.getTrailer().setField(new Signature("FOO"));
+ myMessage.getTrailer().setField(new SignatureLength(3));
+ assertTrue(myMessage.toString().contains("89=FOO\00193=3\001"));
+ }
+
+ @Test
+ public void testFix5HeaderFields() {
+ assertTrue(Message.isHeaderField(ApplVerID.FIELD));
+ assertTrue(Message.isHeaderField(CstmApplVerID.FIELD));
+ }
+
+ @Test
+ public void testApplExtIDIsHeaderField() {
+ assertTrue(Message.isHeaderField(ApplExtID.FIELD));
+ }
+
+ @Test
+ public void testHeaderFieldsMissing() throws Exception {
+ try {
+ new Message("1=FIX.4.2");
+ } catch (final InvalidMessage e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testMessageFromString() {
+ Message message = null;
+
+ boolean badMessage = false;
+ try {
+ message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=036\001");
+ } catch (final InvalidMessage e) {
+ badMessage = true;
+ }
+ assertTrue("Message should be invalid", badMessage);
+
+ try {
+ message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001");
+ } catch (final InvalidMessage e) {
+ fail("Message should be valid (" + e.getMessage() + ")");
+ }
+ assertEquals("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001", message.toString());
+ }
+
+ @Test
+ public void testIsEmpty() {
+ final Message message = new Message();
+ assertTrue("Message should be empty on construction", message.isEmpty());
+ message.getHeader().setField(new BeginString("FIX.4.2"));
+ assertFalse("Header should contain a field", message.isEmpty());
+ message.clear();
+ assertTrue("Message should be empty after clear", message.isEmpty());
+ message.setField(0, new Field(20000, "MSFT"));
+ assertFalse("Body should contain a field", message.isEmpty());
+ message.clear();
+ assertTrue("Message should be empty after clear", message.isEmpty());
+ message.getTrailer().setField(new CheckSum("10"));
+ assertFalse("Trailer should contain a field", message.isEmpty());
+ message.clear();
+ assertTrue("Message should be empty after clear", message.isEmpty());
+ }
+
+ @Test
+ public void testMessageSetGetString() {
+ final Message message = new Message();
+
+ try {
+ message.getString(5);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ message.setString(5, "string5");
+
+ try {
+ assertEquals("string5", message.getString(5));
+ } catch (final FieldNotFound e) {
+ fail("exception thrown");
+ }
+
+ expectedException.expect(FieldException.class);
+ message.setString(100, null);
+ }
+
+ @Test
+ public void testMessageSetGetBoolean() {
+ final Message message = new Message();
+
+ try {
+ message.getBoolean(7);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ message.setBoolean(7, true);
+
+ try {
+ assertTrue(message.getBoolean(7));
+ } catch (final FieldNotFound e) {
+ fail("exception thrown");
+ }
+ }
+
+ @Test
+ public void testMessageSetGetChar() {
+ final Message message = new Message();
+
+ try {
+ message.getChar(12);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ message.setChar(12, 'a');
+
+ try {
+ assertEquals('a', message.getChar(12));
+ } catch (final FieldNotFound e) {
+ fail("exception thrown");
+ }
+ }
+
+ @Test
+ public void testMessageSetGetChars() throws FieldNotFound {
+ final Message message = new Message();
+
+ try {
+ message.getChars(18);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ message.setChars(18, 'a', 'b', '4');
+ assertArrayEquals(new char[]{'a', 'b', '4'}, message.getChars(18));
+ }
+
+ @Test
+ public void testMessageSetGetCharsInvalidFormatException() throws FieldNotFound {
+ expectedException.expect(FieldException.class);
+ expectedException.expectMessage("invalid char array: [65, 32, 98, 32, 48, 53]");
+
+ final Message message = new Message();
+ message.setString(123, "A b 05");
+ message.getChars(123);
+ }
+
+ @Test
+ public void testMessageSetGetInt() {
+ final Message message = new Message();
+
+ try {
+ message.getInt(56);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ message.setInt(56, 23);
+
+ try {
+ assertEquals(23, message.getInt(56));
+ } catch (final FieldNotFound e) {
+ fail("exception thrown");
+ }
+ }
+
+ @Test
+ public void testMessageSetGetDouble() {
+ final Message message = new Message();
+
+ try {
+ message.getDouble(9812);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ message.setDouble(9812, 12.3443);
+
+ try {
+ assertEquals(12.3443, message.getDouble(9812), 1e-10);
+ } catch (final FieldNotFound e) {
+ fail("exception thrown");
+ }
+ }
+
+ @Test
+ public void testMessageSetGetUtcTimeStamp() {
+ final Message message = new Message();
+
+ try {
+ message.getUtcTimeStamp(8);
+ fail("exception not thrown");
+ } catch (final FieldNotFound e) {
+ }
+
+ final TimeZone timezone = TimeZone.getTimeZone("GMT+0");
+ final Calendar calendar = Calendar.getInstance(timezone);
+ calendar.set(2002, 8, 6, 12, 34, 56);
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ final LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(calendar.getTimeInMillis()), ZoneOffset.UTC);
+ message.setUtcTimeStamp(8, time);
+
+ try {
+ assertEquals(message.getUtcTimeStamp(8), time);
+ } catch (final FieldNotFound e) {
+ fail("exception thrown");
+ }
+ }
+
+ @Test
+ public void testRemoveField() {
+ final Message message = new Message();
+ message.setField(new StringField(12, "value"));
+ assertTrue(message.isSetField(12));
+ message.removeField(12);
+ assertTrue(!message.isSetField(12));
+ }
+
+ @Test
+ public void testMessageIterator() {
+ Message message = new Message();
+ java.util.Iterator> i = message.iterator();
+ assertFalse(i.hasNext());
+ try {
+ assertNull(i.next());
+ fail("exception not thrown");
+ } catch (final java.util.NoSuchElementException e) {
+ }
+
+ try {
+ message = new Message("8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001");
+ i = message.iterator();
+ assertTrue(i.hasNext());
+ StringField field = (StringField) i.next();
+ assertEquals(108, field.getField());
+ assertEquals("30", field.getValue());
+
+ assertFalse(i.hasNext());
+ try {
+ assertNull(i.next());
+ fail("exception not thrown");
+ } catch (final java.util.NoSuchElementException e) {
+ }
+
+ final java.util.Iterator> j = message.getHeader().iterator();
+ assertTrue(j.hasNext());
+ field = (StringField) j.next();
+ assertEquals(8, field.getField());
+ assertEquals("FIX.4.2", field.getValue());
+ field = (StringField) j.next();
+ assertEquals(9, field.getField());
+ assertEquals("12", field.getValue());
+ field = (StringField) j.next();
+ assertEquals(35, field.getField());
+ assertEquals("A", field.getValue());
+
+ assertFalse(j.hasNext());
+ try {
+ assertNull(j.next());
+ fail("exception not thrown");
+ } catch (final java.util.NoSuchElementException e) {
+ }
+ } catch (final InvalidMessage e) {
+ fail("exception thrown");
+ }
+ }
+
+ @Test
+ public void testIsAdmin() {
+ final Message message = new Message();
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.HEARTBEAT);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.LOGON);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.LOGOUT);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.SEQUENCE_RESET);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.RESEND_REQUEST);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.TEST_REQUEST);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.REJECT);
+ assertTrue(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.NEW_ORDER_SINGLE);
+ assertFalse(message.isAdmin());
+
+ message.getHeader().setString(MsgType.FIELD, MsgType.QUOTE_RESPONSE);
+ assertFalse(message.isAdmin());
+ }
+
+
+ /**
+ * Verify that an empty message can still be "printed" and doesn't result in any exceptions
+ */
+ @Test
+ public void testEmptyMessageToString() throws Exception {
+ final Message msg = new quickfix.Message();
+ assertNotNull(msg.toString());
+ assertTrue("empty message contains no checksum", msg.toString().length() > 0);
+ }
+
+ @Test
+ public void testParseEmptyString() throws Exception {
+ final String data = "";
+
+ // with validation
+ try {
+ new Message(data, DataDictionaryTest.getDictionary());
+ } catch (final InvalidMessage im) {
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ fail("InvalidMessage expected, got " + e.getClass().getName());
+ }
+
+ // without validation
+ try {
+ new Message(data, DataDictionaryTest.getDictionary(), false);
+ } catch (final InvalidMessage im) {
+ } catch (final Throwable e) {
+ e.printStackTrace();
+ fail("InvalidMessage expected, got " + e.getClass().getName());
+ }
+ }
+
+ /**
+ * Test for data fields with SOH. This test is based on report from a user on
+ * the QuickFIX mailing list. The problem was the user's configuration but this
+ * seems like a good unit test to keep in the suite.
+ */
+ @Test
+ public void testDataFieldParsing() throws Exception {
+ final String data = "10001=Canonical.1.00\00110002=001058\00125001=01\00110003=SAPI_ADMRESP\00110004=SUBSCRIBE_RESP\001"
+ + "10009=705\00110012=01\00110005=SPGW\00110006=SAPI\00110007=0\00110010=16:25:11.537\001"
+ + "10045=SDQADL:01:/SDB/ENT/@/@/STKSDLL:7\00110955=Y\00110963=043\00110961=03\00111285=N\001"
+ + "11339=823,980\00110919=N\00111111=86795696\00110898=043\00110920=~\00110938=N\00111340=5- 9.99\001"
+ + "11343=0.20\00111344=~\00111341=~\00111342=0.15\00111345=10- 14.99\00111348=0.25\00111349=~\00111346=~\001"
+ + "11347=0.15\00111350=15- 19.99\00111353=0.30\00111354=~\00111351=~\00111352=0.20\00111338=23SEP05\001"
+ + "10981=0\00110485=N\00110761=0\00111220=~\00111224=N\00110808=N\00110921=~\00110960=N\00110957=N\00111329=N\001"
+ + "11286=0\00111214=USA\00110917=Y\00111288=0\00110906=N\00110737=0.01\00110956=~\00110967=~\00110965=~\00110809=0\001"
+ + "10762=N\00110763=N\00110712=1\00110905=09:30:00\00110918=YA0101\00110951=Y\00110469=1\00110949=1\00110487=Q\00110950=Y\001"
+ + "10899=N\00110380=N\00110696=03\00111082=18.41\00110217=12\00110954=N\00110708=E\00110958=N\00111213=US \00111334=N\001"
+ + "11332=N\00111331=N\00111330=N\00111335=N\00111333=N\00110767=3\00110974=~\00110980=AIRTRAN HOLDINGS \00111289=N\001"
+ + "10912=4\00110915=0501\00110914=0501\00110975=N\00110913=SLK\00110698=055\00110666=AAI\00110903=S\00111328=N\001"
+ + "10624=L\00111287=0\00110699=0\00110962=L\00111227=SUB1\00111229=5\00111228=1\00111236=16:24:41.521\00111277=16:25:11.630\001";
+
+ try {
+ final DataDictionary dictionary = DataDictionaryTest.getDictionary();
+ final Message m = new Message(("8=FIX.4.4\0019=1144\00135=A\001"
+ + "98=0\001384=2\001372=D\001385=R\001372=8\001385=S\00195=1092\001" + "96="
+ + data + "\00110=5\001"), dictionary);
+ assertEquals(1144, m.bodyLength());
+ final Message m2 = new Message(m.toString(), dictionary);
+ assertEquals(1144, m2.bodyLength());
+ } catch (final InvalidMessage e) {
+ fail(e.getMessage());
+ }
+ }
+
+ @Test
+ public void testHeaderFieldInBody() throws Exception {
+ final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001"
+ + "98=0\001212=4\001384=2\001372=D\001385=R\001372=8\001385=S\00110=103\001",
+ DataDictionaryTest.getDictionary());
+
+ assertFalse(message.hasValidStructure());
+
+ assertTrue(message.getHeader().isSetField(212));
+
+ assertEquals(SessionRejectReason.TAG_SPECIFIED_OUT_OF_REQUIRED_ORDER, message
+ .getException().getSessionRejectReason());
+ assertEquals(212, message.getException().getField());
+ }
+
+ @Test
+ public void testTrailerFieldInBody() throws Exception {
+ final Message message = new Message("8=FIX.4.2\0019=40\00135=A\001"
+ + "98=0\00193=5\001384=2\001372=D\001385=R\001372=8\001385=S\00110=63\001",
+ DataDictionaryTest.getDictionary());
+
+ assertFalse(message.hasValidStructure());
+
+ final SignatureLength signatureLength = new SignatureLength();
+ message.getTrailer().getField(signatureLength);
+ assertEquals(5, signatureLength.getValue());
+ }
+
+ // Includes test for QFJ-413. Repeating group check for size = 0
+ @Test
+ public void testMessageGroupCountValidation() throws Exception {
+ final String data = "8=FIX.4.4\0019=222\00135=D\00149=SenderCompId\00156=TargetCompId\00134=37\001" +
+ "52=20070223-22:28:33\00111=183339\00122=8\00138=1\00140=2\00144=12\00148=BHP\00154=2\001" +
+ "55=BHP\00159=1\00160=20060223-22:38:33\001526=3620\00178=0\00179=AllocACC1\00180=1010.1\001" +
+ "79=AllocACC2\00180=2020.2\001453=2\001448=8\001447=D\001452=4\001448=AAA35354\001447=D\001452=3\00110=079\001";
+ final Message message = new Message();
+ final DataDictionary dd = DataDictionaryTest.getDictionary();
+ message.fromString(data, dd, true);
+ try {
+ dd.validate(message);
+ fail("No exception thrown");
+ } catch (final FieldException e) {
+ final String emsg = e.getMessage();
+ assertNotNull("No exception message", emsg);
+ assertTrue(emsg.startsWith("Incorrect NumInGroup"));
+ }
+ }
+
+ /**
+ * QFJ-760
+ */
+ @Test
+ public void testMessageWithMissingChecksumField() throws Exception {
+ // checksum is "merged" into field 452, i.e. SOH is missing between field 452 and 10
+ String badMessage = "8=FIX.4.4\0019=275\00135=D\00134=3\00149=441000-XXXXX-X-XXXX-001\001" +
+ "52=20131113-10:22:31.567\00156=XXXXX\0011=A1\00111=9fef3663330e209e1bce\00118=H\001" +
+ "22=4\00138=200\00140=M\00148=XX0005519XXXX\00154=1\00155=[N/A]\00158=MassTest\00159=0\001" +
+ "60=20131113-10:22:31.567\001100=XXXX\001526=9fef3663330e209e1bce\001453=1\001" +
+ "448=XXXXXXXX030\001447=D\001452=3610=016\001";
+
+ Message msg = new Message();
+ try {
+ msg.fromString(badMessage, DataDictionaryTest.getDictionary(), true);
+ fail();
+ } catch (final InvalidMessage e) {
+ final String emsg = e.getMessage();
+ assertNotNull("No exception message", emsg);
+ assertTrue(emsg.startsWith("Field not found"));
+ }
+ }
+
+ @Test
+ public void testFalseMessageStructureException() {
+ try {
+ final DataDictionary dd = DataDictionaryTest.getDictionary();
+ // duplicated tag 98
+ // QFJ-65
+ new Message("8=FIX.4.4\0019=22\00135=A\00198=0\00198=0\001108=30\00110=223\001", dd,
+ true);
+ // For now, this will not cause an exception if the length and checksum are correct
+ } catch (final Exception e) {
+ final String text = e.getMessage();
+ assertTrue("Wrong exception message: " + text, !text.contains("Actual body length"));
+ }
+ }
+
+ @Test
+ public void testComponentInGroup() {
+ try {
+ final DataDictionary dd = DataDictionaryTest.getDictionary();
+ // duplicated tag 98
+ // QFJ-65
+ // 8=FIX.4.4\0019=941\00135=AE\00149=ICE\00134=63\00152=20091117-18:59:04.780\00156=XXXX\001
+ // 57=X\001571=219449\001487=0\001856=0\001828=0\001150=F\00117=44750544433\00139=2\001
+ // 570=N\00155=480120\00148=WBS FMG0010-BRN FMG0010\00122=8\001461=FXXXXX\001916=20100201\001
+ // 917=20100228\00132=1.0\00131=0.69\0019018=1\0019022=1\00175=20091117\00160=20091117-18:59:04.775\001
+ // 552=1\00154=2\00137=41296064\00111=557859232\001453=7\001448=trader\001447=D\001452=11\001
+ // 448=Trading Corp\001447=D\001452=13\001448=2757\001447=D\001452=56\001448=805\001447=D\001
+ // 452=4\001448=11122556\001447=D\001452=51\001448=FCM\001447=D\001452=60\001448=U\001447=D\001
+ // 452=5 4\00158=41293051\001555=2\001600=460130\001602=WBS FMG0010!\001603=8\001608=FXXXXX\001
+ // 624=2\001637=80.78\001687=1.0\001654=41296074\0019019=1\0019023=1\0019020=20100201\001
+ // 9021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001525=D\001538=51\001
+ // 524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001
+ // 602=BRN FMG0010! 63=8 608-FXXXXX 624=1 637=80.09 687=1.0 654=41296073 9019=1 9023=1 9020=20100201 9021=20100228 539=4 524=805\001
+ // 525=D\001538=4\001524=11122556 525=D\001538=51 524=Newedge 525=D 538=60 524=U 525=D 538=54 10=112
+ new Message(
+ "8=FIX.4.4\0019=941\00135=AE\00149=ICE\00134=63\00152=20091117-18:59:04.780\00156=XXXX\001" +
+ "57=X\001571=219449\001487=0\001856=0\001828=0\001150=F\00117=44750544433\00139=2\001" +
+ "570=N\00155=480120\00148=WBS FMG0010-BRN FMG0010\00122=8\001461=FXXXXX\001916=20100201\001" +
+ "917=20100228\00132=1.0\00131=0.69\0019018=1\0019022=1\00175=20091117\00160=20091117-18:59:04.775\001" +
+ "552=1\00154=2\00137=41296064\00111=557859232\001453=7\001448=trader\001447=D\001452=11\001" +
+ "448=Trading Corp\001447=D\001452=13\001448=2757\001447=D\001452=56\001448=805\001447=D\001" +
+ "452=4\001448=11122556\001447=D\001452=51\001448=FCM\001447=D\001452=60\001448=U\001447=D\001" +
+ "452=5 4\00158=41293051\001555=2\001600=460130\001602=WBS FMG0010!\001603=8\001608=FXXXXX\001" +
+ "624=2\001637=80.78\001687=1.0\001654=41296074\0019019=1\0019023=1\0019020=20100201\001" +
+ "9021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001525=D\001538=51\001" +
+ "524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001602=BRN FMG0010!\001" +
+ "63=8 608-FXXXXX\001624=1\001637=80.09\001687=1.0\001654=41296073\0019019=1\0019023=1\001" +
+ "9020=20100201\001021=20100228\001539=4\001524=805\001525=D\001538=4\001524=11122556\001" +
+ "525=D\001538=51\001524=FCM\001525=D\001538=60 524=U\001525=D\001538=54\001600=217927\001" +
+ "602=BRN FMG0010!\00163=8 608-FXXXXX\001624=1\001637=80.09\001687=1.0\001654=41296073\001" +
+ "9019=1\0019023=1\0019020=20100201\001021=20100228\001",
+ dd, true);
+ // For now, this will not cause an exception if the length and checksum are correct
+ } catch (final Exception e) {
+ final String text = e.getMessage();
+ assertTrue("Wrong exception message: " + text, !text.contains("Actual body length"));
+ }
+ }
+
+ @Test
+ public void testFalseMessageStructureException2() {
+ try {
+ final DataDictionary dd = DataDictionaryTest.getDictionary();
+ // duplicated raw data length
+ // QFJ-121
+ new Message("8=FIX.4.4\0019=22\00135=A\00196=X\001108=30\00110=223\001", dd, true);
+ } catch (final Exception e) {
+ final String text = e.getMessage();
+ assertTrue("Wrong exception message: " + text,
+ text != null && !text.contains("Actual body length"));
+ }
+ }
+
+ // QFJ-770/QFJ-792
+ @Test
+ public void testRepeatingGroupCountWithUnknownFields() throws Exception {
+ String test = "8=FIX.4.4|9=431|35=d|49=1|34=2|52=20140117-18:20:26.629|56=3|57=21|322=388721|"
+ + "323=4|320=1|393=42|82=1|67=1|711=1|311=780508|309=text|305=8|463=FXXXXX|307=text|542=20140716|"
+ + "436=10.0|9013=1.0|9014=1.0|9017=10|9022=1|9024=1.0|9025=Y|916=20140701|917=20150731|9201=23974|"
+ + "9200=17|9202=text|9300=727|9301=text|9302=text|9303=text|998=text|9100=text|9101=text|9085=text|"
+ + "9083=0|9084=0|9061=579|9062=text|9063=text|9032=10.0|9002=F|9004=780415|9005=780503|10=223|";
+
+ DataDictionary dictionary = new DataDictionary(DataDictionaryTest.getDictionary());
+ Message message = new Message();
+ message.fromString(test.replaceAll("\\|", "\001"), dictionary, true);
+ Group group = message.getGroup(1, 711);
+ String underlyingSymbol = group.getString(311);
+ assertEquals("780508", underlyingSymbol);
+ }
+
+ @Test
+ // QFJ-940
+ public void testRawString() throws Exception {
+
+ String test = "8=FIX.4.4|9=431|35=d|49=1|34=2|52=20140117-18:20:26.629|56=3|57=21|322=388721|"
+ + "323=4|320=1|393=42|82=1|67=1|711=1|311=780508|309=text|305=8|463=FXXXXX|307=text|542=20140716|"
+ + "436=10.0|9013=1.0|9014=1.0|9017=10|9022=1|9024=1.0|9025=Y|916=20140701|917=20150731|9201=23974|"
+ + "9200=17|9202=text|9300=727|9301=text|9302=text|9303=text|998=text|9100=text|9101=text|9085=text|"
+ + "9083=0|9084=0|9061=579|9062=text|9063=text|9032=10.0|9002=F|9004=780415|9005=780503|10=223|";
+
+ DataDictionary dictionary = new DataDictionary(DataDictionaryTest.getDictionary());
+ Message message = new Message();
+ message.fromString(test.replaceAll("\\|", "\001"), dictionary, true);
+ assertEquals(test, message.toRawString().replaceAll("\001", "\\|"));
+ }
+
+ // QFJ-722
+ @Test
+ public void testIfMessageHeaderIsCreatedWithEveryConstructor() throws Exception {
+ final String rawMessage = "8=FIX.4.2\0019=12\00135=A\001108=30\00110=026\001";
+ final DataDictionary dataDictionary = new DataDictionary(DataDictionaryTest.getDictionary());
+
+ final Message emptyConstructor = new Message();
+ assertNotNull(emptyConstructor.getHeader());
+
+ final Message secondConstructor = new Message(new int[]{});
+ assertNotNull(secondConstructor.getHeader());
+
+ final Message thirdConstructor = new Message(rawMessage);
+ assertNotNull(thirdConstructor.getHeader());
+
+ final Message fourthConstructor = new Message(rawMessage, false);
+ assertNotNull(fourthConstructor.getHeader());
+
+ final Message fifthConstructor = new Message(rawMessage, dataDictionary);
+ assertNotNull(fifthConstructor.getHeader());
+
+ final Message sixthConstructor = new Message(rawMessage, dataDictionary, false);
+ assertNotNull(sixthConstructor.getHeader());
+
+ final Message seventhConstructor = new Message(rawMessage, dataDictionary, dataDictionary, false);
+ assertNotNull(seventhConstructor.getHeader());
+ }
+
+ // QFJ-66 Should not throw exception when parsing data field in header
+ @Test
+ public void testHeaderDataField() throws Exception {
+ final Message m = new Message("8=FIX.4.2\0019=53\00135=A\00190=4\00191=ABCD\001"
+ + "98=0\001384=2\001372=D\001385=R\001372=8\001385=S\00110=241\001",
+ DataDictionaryTest.getDictionary());
+ assertEquals("ABCD", m.getHeader().getString(SecureData.FIELD));
+ }
+}
diff --git a/quickfixj-base/src/test/java/quickfix/MessageUtilsTest.java b/quickfixj-base/src/test/java/quickfix/MessageUtilsTest.java
new file mode 100644
index 0000000000..fdde176b1a
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/MessageUtilsTest.java
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * Copyright (c) quickfixengine.org All rights reserved.
+ *
+ * This file is part of the QuickFIX FIX Engine
+ *
+ * This file may be distributed under the terms of the quickfixengine.org
+ * license as defined by quickfixengine.org and appearing in the file
+ * LICENSE included in the packaging of this file.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+ * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * See http://www.quickfixengine.org/LICENSE for licensing information.
+ *
+ * Contact ask@quickfixengine.org if any conditions of this licensing
+ * are not clear to you.
+ ******************************************************************************/
+
+package quickfix;
+
+import quickfix.field.BeginString;
+import quickfix.field.MsgType;
+import quickfix.field.SenderCompID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+/**
+ * NOTE: There are two MessageUtilsTests.
+ * One in quickfixj-base, one in quickfixj-core, which each test
+ * some functionality. This test excludes some test cases that cannot
+ * be tested in this module due to classes that are generated in a
+ * later step, e.g. MessageFactories.
+ */
+public class MessageUtilsTest {
+
+ @Test
+ public void testGetStringField() throws Exception {
+ String messageString = "8=FIX.4.2\0019=12\00135=X\001108=30\00110=049\001";
+ assertEquals("wrong value", "FIX.4.2", MessageUtils.getStringField(messageString,
+ BeginString.FIELD));
+ assertEquals("wrong value", "X", MessageUtils.getStringField(messageString, MsgType.FIELD));
+ assertNull(messageString, MessageUtils.getStringField(messageString, SenderCompID.FIELD));
+ }
+
+ @Test
+ public void testSessionIdFromRawMessage() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" +
+ "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001";
+ SessionID sessionID = MessageUtils.getSessionID(messageString);
+ assertEquals(sessionID.getBeginString(), "FIX.4.0");
+ assertEquals("TW", sessionID.getSenderCompID());
+ assertEquals("ISLD", sessionID.getTargetCompID());
+ }
+
+ @Test
+ public void testReverseSessionIdFromRawMessage() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\00150=TWS\001" +
+ "142=TWL\00152=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001";
+ SessionID sessionID = MessageUtils.getReverseSessionID(messageString);
+ assertEquals(sessionID.getBeginString(), "FIX.4.0");
+ assertEquals("ISLD", sessionID.getSenderCompID());
+ assertEquals("TW", sessionID.getTargetCompID());
+ assertEquals("TWS", sessionID.getTargetSubID());
+ assertEquals("TWL", sessionID.getTargetLocationID());
+ }
+
+ @Test
+ public void testMessageType() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00135=A\00134=1\00149=TW\001" +
+ "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001";
+ assertEquals("A", MessageUtils.getMessageType(messageString));
+ }
+
+ @Test
+ public void testMessageTypeError() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" +
+ "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001";
+ try {
+ MessageUtils.getMessageType(messageString);
+ fail("expected exception");
+ } catch (InvalidMessage e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testMessageTypeError2() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00135=1";
+ try {
+ MessageUtils.getMessageType(messageString);
+ fail("expected exception");
+ } catch (InvalidMessage e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testGetNonexistentStringField() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" +
+ "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223\001";
+ assertNull(MessageUtils.getStringField(messageString, 35));
+ }
+
+ @Test
+ public void testGetStringFieldWithBadValue() throws Exception {
+ String messageString = "8=FIX.4.0\0019=56\00134=1\00149=TW\001" +
+ "52=20060118-16:34:19\00156=ISLD\00198=0\001108=2\00110=223";
+ assertNull(MessageUtils.getStringField(messageString, 10));
+ }
+
+}
diff --git a/quickfixj-base/src/test/java/quickfix/MockSystemTimeSource.java b/quickfixj-base/src/test/java/quickfix/MockSystemTimeSource.java
new file mode 100644
index 0000000000..3c9dea46c4
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/MockSystemTimeSource.java
@@ -0,0 +1,71 @@
+/*******************************************************************************
+ * Copyright (c) quickfixengine.org All rights reserved.
+ *
+ * This file is part of the QuickFIX FIX Engine
+ *
+ * This file may be distributed under the terms of the quickfixengine.org
+ * license as defined by quickfixengine.org and appearing in the file
+ * LICENSE included in the packaging of this file.
+ *
+ * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
+ * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE.
+ *
+ * See http://www.quickfixengine.org/LICENSE for licensing information.
+ *
+ * Contact ask@quickfixengine.org if any conditions of this licensing
+ * are not clear to you.
+ ******************************************************************************/
+
+package quickfix;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Calendar;
+
+public class MockSystemTimeSource implements SystemTimeSource {
+ private long[] systemTimes = { System.currentTimeMillis() };
+ private int offset;
+
+ public MockSystemTimeSource() {
+ // empty
+ }
+
+ public MockSystemTimeSource(long time) {
+ setSystemTimes(time);
+ }
+
+ public void setSystemTimes(long[] times) {
+ systemTimes = times;
+ }
+
+ void setSystemTimes(long time) {
+ systemTimes = new long[] { time };
+ }
+
+ public void setTime(Calendar c) {
+ setSystemTimes(c.getTimeInMillis());
+ }
+
+ @Override
+ public long getTime() {
+ if (systemTimes.length - offset > 1) {
+ offset++;
+ }
+ return systemTimes[offset];
+ }
+
+ public void increment(long delta) {
+ if (systemTimes.length - offset == 1) {
+ systemTimes[offset] += delta;
+ }
+ }
+
+ @Override
+ public LocalDateTime getNow() {
+ // TODO maybe we need nano-precision later on
+ return LocalDateTime.ofInstant(Instant.ofEpochMilli(getTime()), ZoneOffset.UTC);
+ }
+
+}
diff --git a/quickfixj-core/src/test/java/quickfix/SessionIDTest.java b/quickfixj-base/src/test/java/quickfix/SessionIDTest.java
similarity index 100%
rename from quickfixj-core/src/test/java/quickfix/SessionIDTest.java
rename to quickfixj-base/src/test/java/quickfix/SessionIDTest.java
diff --git a/quickfixj-base/src/test/java/quickfix/UTCDateOnlyFieldTest.java b/quickfixj-base/src/test/java/quickfix/UTCDateOnlyFieldTest.java
new file mode 100644
index 0000000000..66ac2a6f2e
--- /dev/null
+++ b/quickfixj-base/src/test/java/quickfix/UTCDateOnlyFieldTest.java
@@ -0,0 +1,38 @@
+package quickfix;
+
+import org.junit.Test;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeParseException;
+
+import static org.junit.Assert.*;
+
+public class UTCDateOnlyFieldTest {
+
+ public static final int MD_ENTRY_DATE_FIELD = 272;
+
+ @Test
+ public void testUtcDateOnlyFieldFromLocalDate() {
+ LocalDate localDate = LocalDate.of(2023, 3, 19);
+ UtcDateOnlyField utcDateOnlyField = new UtcDateOnlyField(MD_ENTRY_DATE_FIELD,localDate);
+ assertTrue(utcDateOnlyField.valueEquals(localDate));
+ assertEquals(localDate,utcDateOnlyField.getValue());
+ assertEquals(MD_ENTRY_DATE_FIELD, utcDateOnlyField.getField());
+ }
+
+ @Test
+ public void testUtcDateOnlyFieldFromNow() {
+ UtcDateOnlyField utcDateOnlyField = new UtcDateOnlyField(MD_ENTRY_DATE_FIELD);
+ assertNotNull(utcDateOnlyField.getValue());
+ assertEquals(MD_ENTRY_DATE_FIELD, utcDateOnlyField.getField());
+ }
+
+ @Test
+ public void testAssignment() {
+ LocalDate localDate = LocalDate.of(2023, 12, 31);
+ UtcDateOnlyField utcDateOnlyField = new UtcDateOnlyField(MD_ENTRY_DATE_FIELD);
+ utcDateOnlyField.setValue(localDate);
+ assertTrue(utcDateOnlyField.valueEquals(localDate));
+ assertEquals(localDate,utcDateOnlyField.getValue());
+ }
+}
diff --git a/quickfixj-core/src/test/resources/FIXT11_Custom_Test.xml b/quickfixj-base/src/test/resources/FIXT11_Custom_Test.xml
similarity index 100%
rename from quickfixj-core/src/test/resources/FIXT11_Custom_Test.xml
rename to quickfixj-base/src/test/resources/FIXT11_Custom_Test.xml
diff --git a/quickfixj-base/src/test/resources/FIX_External_DTD.xml b/quickfixj-base/src/test/resources/FIX_External_DTD.xml
new file mode 100644
index 0000000000..b1953366fd
--- /dev/null
+++ b/quickfixj-base/src/test/resources/FIX_External_DTD.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-base/src/test/resources/configWithSessionVariables.ini b/quickfixj-base/src/test/resources/configWithSessionVariables.ini
new file mode 100644
index 0000000000..e06f510dbc
--- /dev/null
+++ b/quickfixj-base/src/test/resources/configWithSessionVariables.ini
@@ -0,0 +1,20 @@
+#comment
+[DEFAULT]
+Empty=
+ConnectionType=acceptor
+SocketAcceptPort=5001
+FileStorePath=store
+StartTime=00:00:00
+EndTime=00:00:00
+TestLong=1234
+TestLong2=abcd
+TestDouble=12.34
+TestDouble2=abcd
+TestBoolTrue=Y
+TestBoolFalse=N
+SenderCompID=TW
+
+[SESSION]
+BeginString=FIX.4.2
+TargetCompID=CLIENT3_${CLIENT_PLACEHOLDER1}_${CLIENT_PLACEHOLDER2}
+DataDictionary=../spec/FIX42.xml
diff --git a/quickfixj-core/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def b/quickfixj-base/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def
similarity index 100%
rename from quickfixj-core/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def
rename to quickfixj-base/src/test/resources/quickfix/test/acceptance/definitions/client/Normal.def
diff --git a/quickfixj-class-pruner-maven-plugin/pom.xml b/quickfixj-class-pruner-maven-plugin/pom.xml
new file mode 100644
index 0000000000..a9567f1811
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/pom.xml
@@ -0,0 +1,96 @@
+
+
+
+ 4.0.0
+
+
+ org.quickfixj
+ quickfixj-parent
+ 3.0.0-SNAPSHOT
+
+
+ quickfixj-class-pruner-maven-plugin
+ maven-plugin
+
+ QuickFIX/J Class Pruner Maven Plugin
+
+
+ ${maven.version}
+
+
+
+ UTF-8
+
+
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ provided
+
+
+ org.apache.maven
+ maven-core
+ provided
+
+
+ org.apache.maven
+ maven-artifact
+ provided
+
+
+ org.apache.maven
+ maven-compat
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+
+
+ org.apache.maven.shared
+ file-management
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ org.apache.maven.plugin-testing
+ maven-plugin-testing-harness
+ test
+
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+
+ true
+
+
+
+ mojo-descriptor
+
+ descriptor
+
+
+
+ help-goal
+
+ helpmojo
+
+
+
+
+
+
+
diff --git a/quickfixj-class-pruner-maven-plugin/readme.md b/quickfixj-class-pruner-maven-plugin/readme.md
new file mode 100644
index 0000000000..1f865f9ce6
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/readme.md
@@ -0,0 +1,59 @@
+# Class Pruner Maven Plugin
+
+This plugin has a very specialised purpose.
+
+The purpose is to minimise the number of Java classes and generated sources for packaging artefacts of specific FIX versions. This is needed because the introduction of FIX Latest standard results in a very large number of Fields that in turn requires excessive memory and build time for the creation of Javadoc artefacts.
+
+This is a result of the design of QuickFIX/J in that the Fields package is shared in common for multiple versions of the FIX Protocol. Accordingly the Component and Messages packages are compiled against the common package of Fields. The Messages, Components and Fields can be assembled together as in ```quickfixj-messages-all``` or separately as in ```quickfixj-messages-fix40``` to ```quickfixj-messages-fixlatest``` and ```quickfixj-messages-fixt11```. In each of these assemblies the requisite Fields are included. These packages can be used together at runtime without conflict if the Field classes are identical.
+
+Broadly speaking the later versions of FIX are super-sets of the prior versions. There are cases of Fields being deprecated but in QuickFIX/J distributions these Fields are included in subsequent distributions.
+
+As mentioned the FIX Latest distribution is very large and it is built in its entirety by QuickFIX/J to verify compatibility. The complete set of Fields is not required for older versions of the standard which are still in common use.
+
+This plugin can be used before packaging a version of the standard to prune the Java sources and classes that are not included in the FIX specification for that version. This allows Javadoc to be created and produces a more compact and concise distribution.
+
+The plugin parses the specified QuickFIX dictionaries to identify the required Fields and deletes redundant Java sources and classes from the specified generated sources and classes directories.
+
+It can be used as follows.
+
+```
+...
+
+
+
+ org.quickfixj
+ class-pruner-maven-plugin
+ ${project.version}
+
+
+ prune
+
+ prune
+
+
+ ${project.basedir}/../quickfixj-messages-all/target/classes/quickfix/field
+ ${project.basedir}/../quickfixj-messages-all/target/generated-sources/quickfix/field
+
+ ${project.basedir}/../quickfixj-messages-all/target/classes/
+
+ **/*.xml
+
+
+ **/FIXLatest.xml
+ OrchestraFIXLatest.xml
+ FIX50SP2.xml
+ FIX50SP1.xml
+ FIX44.xml
+
+ false
+
+
+
+
+
+...
+```
+Note:
+* only *later* versions of the FIX specification than the one being packaged are excluded from parsing
+* FIX dictionaries not used in the code generation are excluded (redundant Dictionary files are present in the project)
+* the use of this plugin depends on correct ordering of the modules with the most recent versions of the FIX protocol being packaged before packaging the older versions of the protocol.
diff --git a/quickfixj-class-pruner-maven-plugin/src/main/java/org/quickfixj/ClassPrunerMojo.java b/quickfixj-class-pruner-maven-plugin/src/main/java/org/quickfixj/ClassPrunerMojo.java
new file mode 100644
index 0000000000..874d960ff9
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/main/java/org/quickfixj/ClassPrunerMojo.java
@@ -0,0 +1,205 @@
+package org.quickfixj;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.shared.model.fileset.FileSet;
+import org.apache.maven.shared.model.fileset.util.FileSetManager;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Goal that prunes specified classes
+ * This is intended to be used before javadoc and packaging to reduce memory requirements and build time
+ */
+@Mojo( name = "prune", defaultPhase = LifecyclePhase.INSTALL )
+public class ClassPrunerMojo extends AbstractMojo {
+
+ /**
+ * fileSet
defining the QFJ Dictionary Locations.
+ */
+ @Parameter ( property = "fileset", required = true )
+ private FileSet fileset;
+
+ /**
+ * Location of classes to delete
+ */
+ @Parameter( defaultValue = "classes", property = "classesDirectory", required = true )
+ private File classesDirectory;
+
+ /**
+ * Location of the generated source to delete
+ */
+ @Parameter( defaultValue = "generated-sources", property = "generatedSourcesDirectory", required = true )
+ private File generatedSourcesDirectory;
+
+
+ public void execute()
+ throws MojoExecutionException
+ {
+ this.getLog().info("executing mojo.");
+
+ if ( !classesDirectory.exists() || !classesDirectory.isDirectory() )
+ {
+ String errorMsg = new StringBuilder(classesDirectory.getAbsolutePath()).append(" must exist and be a directory.").toString();
+ this.getLog().error(errorMsg.toString());
+ throw new MojoExecutionException( errorMsg.toString() );
+ } else {
+ this.getLog().info(new StringBuilder("Classes Directory : ").append(classesDirectory.getAbsolutePath()).toString());
+ }
+
+ if ( !generatedSourcesDirectory.exists() || !generatedSourcesDirectory.isDirectory() )
+ {
+ String errorMsg = new StringBuilder(generatedSourcesDirectory.getAbsolutePath()).append(" must exist and be a directory.").toString();
+ this.getLog().error(errorMsg.toString());
+ throw new MojoExecutionException( errorMsg.toString() );
+ } else {
+ this.getLog().info(new StringBuilder("Generated Sources Directory : ").append(generatedSourcesDirectory.getAbsolutePath()).toString());
+ }
+
+ if (null == fileset) {
+ String errorMsg = "filset must not be null.";
+ this.getLog().error(errorMsg);
+ throw new MojoExecutionException( errorMsg );
+ }
+
+ Set fieldNames = new HashSet();
+
+ collectFieldNames(fieldNames);
+
+ try {
+ pruneGeneratedSources(fieldNames);
+ pruneClasses(fieldNames);
+ } catch (IOException e) {
+ String errorMsg = "Exception pruning directories.";
+ this.getLog().error(errorMsg, e);
+ throw new MojoExecutionException( errorMsg, e);
+ }
+ }
+
+ private void pruneClasses(Set fieldNames) throws IOException {
+ prune(fieldNames, this.classesDirectory, ".class", "Java class");
+ }
+
+ private void pruneGeneratedSources(Set fieldNames) throws IOException {
+ prune(fieldNames, this.generatedSourcesDirectory, ".java", "Java source");
+ }
+
+ private void prune(Set fieldNames, File targetDirectory, String fileSuffix, String descriptor) throws IOException {
+ Set files = listFiles(targetDirectory);
+ Set namesOfFilesToKeep = fieldNames.stream().map(file -> new StringBuilder(file).append(fileSuffix).toString()).collect(Collectors.toSet());
+ files.removeAll(namesOfFilesToKeep);
+ List fileList = new ArrayList(files);
+ Collections.sort(fileList);
+ this.getLog().info(descriptor + "s to delete : " + fileList.size());
+ for (String fileName : fileList) {
+ this.getLog().info("Deleting " + descriptor + " : " + fileName);
+ File file = new File( targetDirectory, fileName );
+ Files.delete(file.toPath());
+ }
+ }
+
+ private static Set listFiles(File directory) throws IOException {
+ try (Stream stream = Files.list(directory.toPath())) {
+ return stream
+ .filter(file -> !Files.isDirectory(file))
+ .map(Path::getFileName)
+ .map(Path::toString)
+ .filter(fileName -> !fileName.matches(".*\\.xml"))
+ .collect(Collectors.toSet());
+ }
+ }
+
+ private void collectFieldNames(Set fieldNames) throws MojoExecutionException {
+ Set fileNamesToParse = collectFileNameToParse();
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db;
+ try {
+ db = dbf.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ String msg = "ParserConfigurationException creating Document Builder";
+ this.getLog().error(msg, e);
+ throw new MojoExecutionException(msg, e);
+ }
+
+ for (String fileName: fileNamesToParse) {
+ Document document;
+ try {
+ document = db.parse(new File(fileName));
+ // optional, but recommended
+ // http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
+ document.getDocumentElement().normalize();
+ addNames(document.getDocumentElement(), "fields/field", fieldNames);
+ } catch (SAXException | IOException e) {
+ String msg = "Exception parsing file " + fileName;
+ this.getLog().error(msg, e);
+ throw new MojoExecutionException(msg, e);
+ }
+ }
+
+ List fieldList = new ArrayList(fieldNames);
+ Collections.sort(fieldList);
+ for (String fieldName : fieldList) {
+ this.getLog().info("Found field : " + fieldName);
+ }
+ this.getLog().info("Found field total : " + fieldList.size());
+ }
+
+ private Set collectFileNameToParse() {
+ FileSetManager fileSetManager = new FileSetManager();
+ this.getLog().info("fileset " + fileset.toString());
+
+ String currentDir = System.getProperty("user.dir");
+ this.getLog().info("Current working directory : " + currentDir);
+
+ Set includedFiles = new HashSet(Arrays.asList(fileSetManager.getIncludedFiles( fileset )));
+ Set fileNamesToParse = new HashSet();
+
+ String baseDirectory = fileset.getDirectory();
+ for (String includedFile: includedFiles) {
+ this.getLog().info("will parse file : " + includedFile);
+ String fileName = new StringBuilder(baseDirectory).append(File.separator).append(includedFile).toString();
+ fileNamesToParse.add(fileName);
+ }
+ return fileNamesToParse;
+ }
+
+ private static void addNames(Element element, String path, Set fieldNames) {
+ int separatorOffset = path.indexOf("/");
+ if (separatorOffset == -1) {
+ NodeList fieldNodeList = element.getElementsByTagName(path);
+ for (int i = 0; i < fieldNodeList.getLength(); i++) {
+ fieldNames.add(((Element) fieldNodeList.item(i)).getAttribute("name"));
+ }
+ } else {
+ String tag = path.substring(0, separatorOffset);
+ NodeList subnodes = element.getElementsByTagName(tag);
+ for (int i = 0; i < subnodes.getLength(); i++) {
+ addNames((Element) subnodes.item(i), path.substring(separatorOffset + 1), fieldNames);
+ }
+ }
+ }
+}
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/java/org/quickfixj/ClassPrunerMojoTest.java b/quickfixj-class-pruner-maven-plugin/src/test/java/org/quickfixj/ClassPrunerMojoTest.java
new file mode 100644
index 0000000000..cf94698f3c
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/test/java/org/quickfixj/ClassPrunerMojoTest.java
@@ -0,0 +1,408 @@
+package org.quickfixj;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.maven.plugin.testing.MojoRule;
+import org.apache.maven.shared.model.fileset.FileSet;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.google.common.io.Files;
+
+public class ClassPrunerMojoTest
+{
+ private List classesList = new ArrayList(Arrays.asList(
+ "Account"
+ ,"AccruedInterestAmt"
+ ,"AccruedInterestRate"
+ ,"AdvId"
+ ,"AdvRefID"
+ ,"AdvSide"
+ ,"AdvTransType"
+ ,"AllocAccount"
+ ,"AllocAvgPx"
+ ,"AllocHandlInst"
+ ,"AllocID"
+ ,"AllocLinkID"
+ ,"AllocLinkType"
+ ,"AllocNetMoney"
+ ,"AllocRejCode"
+ ,"AllocShares"
+ ,"AllocStatus"
+ ,"AllocText"
+ ,"AllocTransType"
+ ,"AvgPrxPrecision"
+ ,"AvgPx"
+ ,"BeginSeqNo"
+ ,"BeginString"
+ ,"BidForwardPoints"
+ ,"BidPx"
+ ,"BidSize"
+ ,"BidSpotRate"
+ ,"BodyLength"
+ ,"BrokerOfCredit"
+ ,"CashOrderQty"
+ ,"CashSettlAgentAcctName"
+ ,"CashSettlAgentAcctNum"
+ ,"CashSettlAgentCode"
+ ,"CashSettlAgentContactName"
+ ,"CashSettlAgentContactPhone"
+ ,"CashSettlAgentName"
+ ,"CheckSum"
+ ,"ClientID"
+ ,"ClOrdID"
+ ,"Commission"
+ ,"CommType"
+ ,"CoveredOrUncovered"
+ ,"CumQty"
+ ,"Currency"
+ ,"CustomerOrFirm"
+ ,"CxlQty"
+ ,"CxlRejReason"
+ ,"CxlType"
+ ,"DeliverToCompID"
+ ,"DeliverToLocationID"
+ ,"DeliverToSubID"
+ ,"DKReason"
+ ,"DlvyInst"
+ ,"EffectiveTime"
+ ,"EmailThreadID"
+ ,"EmailType"
+ ,"EncryptMethod"
+ ,"EndSeqNo"
+ ,"ExDestination"
+ ,"ExecBroker"
+ ,"ExecID"
+ ,"ExecInst"
+ ,"ExecRefID"
+ ,"ExecTransType"
+ ,"ExecType"
+ ,"ExpireTime"
+ ,"ForexReq"
+ ,"FutSettDate"
+ ,"FutSettDate2"
+ ,"GapFillFlag"
+ ,"HandlInst"
+ ,"Headline"
+ ,"HeartBtInt"
+ ,"IDSource"
+ ,"IOIID"
+ ,"IOINaturalFlag"
+ ,"IOIOthSvc"
+ ,"IOIQltyInd"
+ ,"IOIQualifier"
+ ,"IOIRefID"
+ ,"IOIShares"
+ ,"IOITransType"
+ ,"Issuer"
+ ,"LastCapacity"
+ ,"LastForwardPoints"
+ ,"LastMkt"
+ ,"LastPx"
+ ,"LastShares"
+ ,"LastSpotRate"
+ ,"LeavesQty"
+ ,"LinesOfText"
+ ,"ListExecInst"
+ ,"ListID"
+ ,"ListNoOrds"
+ ,"ListSeqNo"
+ ,"LocateReqd"
+ ,"MaturityDay"
+ ,"MaturityMonthYear"
+ ,"MaxFloor"
+ ,"MaxShow"
+ ,"MinQty"
+ ,"MiscFeeAmt"
+ ,"MiscFeeCurr"
+ ,"MiscFeeType"
+ ,"MsgSeqNum"
+ ,"MsgType"
+ ,"NetMoney"
+ ,"NewSeqNo"
+ ,"NoAllocs"
+ ,"NoDlvyInst"
+ ,"NoExecs"
+ ,"NoIOIQualifiers"
+ ,"NoMiscFees"
+ ,"NoOrders"
+ ,"NoRelatedSym"
+ ,"NoRpts"
+ ,"NotifyBrokerOfCredit"
+ ,"NumDaysInterest"
+ ,"OfferForwardPoints"
+ ,"OfferPx"
+ ,"OfferSize"
+ ,"OfferSpotRate"
+ ,"OnBehalfOfCompID"
+ ,"OnBehalfOfLocationID"
+ ,"OnBehalfOfSubID"
+ ,"OpenClose"
+ ,"OptAttribute"
+ ,"OrderID"
+ ,"OrderQty"
+ ,"OrderQty2"
+ ,"OrdRejReason"
+ ,"OrdStatus"
+ ,"OrdType"
+ ,"OrigClOrdID"
+ ,"OrigSendingTime"
+ ,"OrigTime"
+ ,"PegDifference"
+ ,"PossDupFlag"
+ ,"PossResend"
+ ,"PrevClosePx"
+ ,"Price"
+ ,"ProcessCode"
+ ,"PutOrCall"
+ ,"QuoteID"
+ ,"QuoteReqID"
+ ,"RawData"
+ ,"RawDataLength"
+ ,"RefAllocID"
+ ,"RefSeqNum"
+ ,"RelatdSym"
+ ,"ReportToExch"
+ ,"ResetSeqNumFlag"
+ ,"RptSeq"
+ ,"Rule80A"
+ ,"SecondaryOrderID"
+ ,"SecureData"
+ ,"SecureDataLen"
+ ,"SecurityDesc"
+ ,"SecurityExchange"
+ ,"SecurityID"
+ ,"SecuritySettlAgentAcctName"
+ ,"SecuritySettlAgentAcctNum"
+ ,"SecuritySettlAgentCode"
+ ,"SecuritySettlAgentContactName"
+ ,"SecuritySettlAgentContactPhone"
+ ,"SecuritySettlAgentName"
+ ,"SecurityType"
+ ,"SenderCompID"
+ ,"SenderLocationID"
+ ,"SenderSubID"
+ ,"SendingTime"
+ ,"SettlBrkrCode"
+ ,"SettlCurrAmt"
+ ,"SettlCurrency"
+ ,"SettlCurrFxRate"
+ ,"SettlCurrFxRateCalc"
+ ,"SettlDeliveryType"
+ ,"SettlDepositoryCode"
+ ,"SettlInstCode"
+ ,"SettlInstID"
+ ,"SettlInstMode"
+ ,"SettlInstSource"
+ ,"SettlInstTransType"
+ ,"SettlLocation"
+ ,"SettlmntTyp"
+ ,"Shares"
+ ,"Side"
+ ,"Signature"
+ ,"SignatureLength"
+ ,"StandInstDbID"
+ ,"StandInstDbName"
+ ,"StandInstDbType"
+ ,"StopPx"
+ ,"StrikePrice"
+ ,"Subject"
+ ,"Symbol"
+ ,"SymbolSfx"
+ ,"TargetCompID"
+ ,"TargetLocationID"
+ ,"TargetSubID"
+ ,"TestReqID"
+ ,"Text"
+ ,"TimeInForce"
+ ,"TradeDate"
+ ,"TransactTime"
+ ,"Urgency"
+ ,"URLLink"
+ ,"ValidUntilTime"
+ ,"ValuationBusinessCenter"
+ ,"ValuationDate"
+ ,"ValuationMethod"
+ ,"ValuationReferenceModel"
+ ,"ValuationSource"
+ ,"ValuationTime"
+ ,"ValueCheckAction"
+ ,"ValueCheckType"
+ ,"ValueOfFutures"
+ ,"VegaMultiplier"
+ ,"VenueType"
+ ,"VerificationMethod"
+ ,"VersusPurchaseDate"
+ ,"VersusPurchasePrice"
+ ,"Volatility"
+ ,"VoluntaryRegulatoryReport"
+ ,"WarningText"
+ ,"WaveNo"
+ ,"WireReference"
+ ,"WorkingIndicator"
+ ,"WtAverageLiquidity"
+ ,"Yield"
+ ,"YieldCalcDate"
+ ,"YieldRedemptionDate"
+ ,"YieldRedemptionPrice"
+ ,"YieldRedemptionPriceType"
+ ,"YieldType"));
+
+ List someFieldNamesThatShouldBePruned =
+ new ArrayList<>(Arrays.asList("ValuationBusinessCenter",
+ "ValuationDate",
+ "ValuationMethod",
+ "ValuationReferenceModel",
+ "ValuationSource",
+ "ValuationTime",
+ "ValueCheckAction",
+ "ValueCheckType",
+ "ValueOfFutures",
+ "VegaMultiplier",
+ "VenueType",
+ "VerificationMethod",
+ "VersusPurchaseDate",
+ "VersusPurchasePrice",
+ "Volatility",
+ "VoluntaryRegulatoryReport",
+ "WarningText",
+ "WireReference",
+ "WorkingIndicator",
+ "WtAverageLiquidity",
+ "Yield",
+ "YieldCalcDate",
+ "YieldRedemptionDate",
+ "YieldRedemptionPrice",
+ "YieldRedemptionPriceType",
+ "YieldType"));
+ private File classesDirectory;
+ private File generatedSourcesDirectory;
+
+ @Rule
+ public MojoRule rule = new MojoRule()
+ {
+ @Override
+ protected void before() throws Throwable
+ {
+ }
+
+ @Override
+ protected void after()
+ {
+ }
+ };
+
+ @After
+ public void clearDown() throws Exception {
+ ClassPrunerMojoTest.clearDownDirectory(generatedSourcesDirectory);
+ ClassPrunerMojoTest.clearDownDirectory(classesDirectory);
+ }
+
+ private static void clearDownDirectory(File directory) throws Exception {
+ if (null != directory && directory.exists()) {
+ FileUtils.cleanDirectory(directory);
+ }
+ directory.delete();
+ }
+
+ /**
+ * This test represents pruning Field classes that do not exist if FIX 5.0sp2 and earlier.
+ * The inputs are QFJ dictionaries for FIX 5.0sp2 (and some earlier versions) and
+ * directories that contain files with the names of Fields found in FIX Latest.
+ * The files for Fields found in FIX 5.0sp2 and earlier are retained, the excess files are pruned.
+ * @throws Exception if any
+ */
+ @Test
+ public void testPrune()
+ throws Exception
+ {
+ File pom = new File("target/test-classes/project-to-test/");
+ assertNotNull( pom );
+ assertTrue( pom.exists() );
+
+ ClassPrunerMojo myMojo = ( ClassPrunerMojo ) rule.lookupConfiguredMojo( pom, "prune" );
+ assertNotNull( myMojo );
+
+ classesDirectory = ( File ) rule.getVariableValueFromObject( myMojo, "classesDirectory" );
+ assertNotNull( classesDirectory );
+ if (!classesDirectory.exists()) {
+ classesDirectory.mkdirs();
+ }
+ //TODO create directory, populate with test files
+ myMojo.getLog().info("classesDirectory : " + classesDirectory.getAbsolutePath() );
+ createFilesForTest(classesList, classesDirectory, ".class");
+ createFilesForTest(someFieldNamesThatShouldBePruned, classesDirectory, ".class");
+
+ generatedSourcesDirectory = ( File ) rule.getVariableValueFromObject( myMojo, "generatedSourcesDirectory" );
+ assertNotNull( generatedSourcesDirectory );
+ if (!generatedSourcesDirectory.exists()) {
+ generatedSourcesDirectory.mkdirs();
+ }
+ myMojo.getLog().info("generatedSourcesDirectory : " + generatedSourcesDirectory.getAbsolutePath() );
+ createFilesForTest(classesList, generatedSourcesDirectory, ".java");
+ createFilesForTest(someFieldNamesThatShouldBePruned, generatedSourcesDirectory, ".java");
+
+ myMojo.execute();
+
+ Object variableValueFromObject = rule.getVariableValueFromObject( myMojo, "fileset" );
+ FileSet dictionaryFileSet = ( FileSet ) variableValueFromObject;
+ assertNotNull( dictionaryFileSet );
+
+ // The following are based on files created above
+ List someFieldNamesThatShouldStillExist = new ArrayList<>(Arrays.asList("WaveNo", "ValidUntilTime", "Account"));
+
+ for (String fieldName : someFieldNamesThatShouldStillExist) {
+ File source = new File( generatedSourcesDirectory, fieldName.concat(".java") );
+ assertTrue( source.exists() );
+ File clazz = new File( classesDirectory, fieldName.concat(".class") );
+ assertTrue( clazz.exists() );
+ }
+
+ for (String fieldName : someFieldNamesThatShouldBePruned) {
+ File source = new File( generatedSourcesDirectory, fieldName.concat(".java") );
+ assertFalse( source.exists() );
+ File clazz = new File( classesDirectory, fieldName.concat(".class") );
+ assertFalse( clazz.exists() );
+ }
+
+ int numberOfFieldsFromTheCombinedDictionaries = 209;
+ assertEquals(classesDirectory.list().length, numberOfFieldsFromTheCombinedDictionaries);
+ assertEquals(generatedSourcesDirectory.list().length, numberOfFieldsFromTheCombinedDictionaries);
+ }
+
+ private static void createFilesForTest(List classesList, File classesDirectory, String extension) throws IOException {
+ classesList.stream().forEach(throwingConsumerWrapper(f -> {File file = new File(classesDirectory, f.concat(extension)); Files.touch(file);}));
+ }
+
+ @FunctionalInterface
+ public interface ThrowingConsumer {
+ void accept(T t) throws E;
+ }
+
+ //https://www.baeldung.com/java-lambda-exceptions
+ static Consumer throwingConsumerWrapper(
+ ThrowingConsumer throwingConsumer) {
+ return i -> {
+ try {
+ throwingConsumer.accept(i);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ };
+ }
+
+}
+
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/project-to-test/pom.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/project-to-test/pom.xml
new file mode 100644
index 0000000000..96513ba048
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/project-to-test/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ org.quickfixj
+ project-to-test
+ 1.0-SNAPSHOT
+ jar
+ Test MyMojo
+
+
+
+
+ org.quickfixj
+ quickfixj-class-pruner-maven-plugin
+
+ not-really-classes
+ not-really-generated-sources
+
+ target/test-classes/qfj-dictionaries
+
+ **/*.xml
+
+
+ **/FIX50SP2.modified.xml
+ **/FIXT11.xml
+
+ false
+
+
+
+
+
+
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX40.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX40.xml
new file mode 100644
index 0000000000..514a89d65f
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX40.xml
@@ -0,0 +1,857 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX41.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX41.xml
new file mode 100644
index 0000000000..458497c537
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX41.xml
@@ -0,0 +1,1268 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX50SP2.modified.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX50SP2.modified.xml
new file mode 100644
index 0000000000..069cf4837b
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIX50SP2.modified.xml
@@ -0,0 +1,9942 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIXT11.xml b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIXT11.xml
new file mode 100644
index 0000000000..d763b29641
--- /dev/null
+++ b/quickfixj-class-pruner-maven-plugin/src/test/resources/qfj-dictionaries/FIXT11.xml
@@ -0,0 +1,383 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-codegenerator/pom.xml b/quickfixj-codegenerator/pom.xml
index 6d8598ee35..fd81e29225 100644
--- a/quickfixj-codegenerator/pom.xml
+++ b/quickfixj-codegenerator/pom.xml
@@ -25,18 +25,33 @@
org.apache.maven
maven-plugin-api
- 3.8.7
+ provided
org.apache.maven
maven-project
- 2.2.1
+ provided
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ provided
net.sf.saxon
Saxon-HE
11.4
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ commons-io
+ commons-io
+ test
+
diff --git a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java
index e0cb060d2a..ec208887cb 100644
--- a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java
+++ b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/CodeGenerationException.java
@@ -23,6 +23,11 @@
* Signals an error in the code generation software.
*/
public class CodeGenerationException extends RuntimeException {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -7143250551383159031L;
+
public CodeGenerationException(Throwable cause) {
super(cause);
}
diff --git a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java
index c94b2fcbff..5faeef257c 100644
--- a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java
+++ b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/GenerateMojo.java
@@ -23,6 +23,9 @@
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.FileUtils;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
import java.io.File;
@@ -30,75 +33,70 @@
* A mojo that uses the quickfix code generator to generate
* Java source files from a QuickFIX Dictionary.
*
- * @goal generate
- * @phase generate-sources
* @description QuickFIX/J code generation plugin
- * @author Claudio Bantaloukas
+ * @author Claudio Bantaloukas
*/
+@Mojo( name = "generate", defaultPhase = LifecyclePhase.GENERATE_SOURCES )
public class GenerateMojo extends AbstractMojo {
/**
* The dictionary file to use for mapping messages to java.
- *
- * @parameter expression="${basedir}/src/main/quickfixj/dictionary/FIX44.xml"
*/
+ @Parameter(defaultValue="${basedir}/src/main/quickfixj/dictionary/FIX44.xml")
private File dictFile;
/**
* The source directory containing *.xsd files.
- *
- * @parameter expression="${basedir}/src/resources/quickfixj/codegenerator"
*/
+ @Parameter(defaultValue="${basedir}/src/resources/quickfixj/codegenerator")
private File schemaDirectory;
/**
* The directory to output the generated sources to.
- *
- * @parameter expression="${project.build.directory}/generated-sources/"
*/
+ @Parameter(defaultValue="${project.build.directory}/generated-sources/")
private File outputDirectory;
/**
* Enable BigDecimal representation.
- *
- * @parameter default-value="false"
*/
+ @Parameter(defaultValue="false")
private boolean decimal;
/**
* Enable orderedFields.
- *
- * @parameter default-value="false"
*/
+ @Parameter(defaultValue="false")
private boolean orderedFields;
/**
* The package for the generated source.
- *
- * @parameter
*/
+ @Parameter(required = true)
private String packaging;
/**
* The base field class to use.
- *
- * @parameter default-value = "quickfix.field"
*/
+ @Parameter(defaultValue = "quickfix.field")
private String fieldPackage = "quickfix.field";
/**
* The default UtcTimestampPrecision to be used during field code generation.
- *
- * @parameter
*/
+ @Parameter(required = false)
private String utcTimestampPrecision;
+ /**
+ * Defines whether the code generator should overwrite existing files with the same name
+ */
+ @Parameter(defaultValue = "true")
+ private boolean overwrite = true;
+
/**
* The Maven project to act upon.
- *
- * @parameter expression="${project}"
- * @required
*/
+ @Parameter(defaultValue = "${project}", required = true)
private MavenProject project;
/**
@@ -143,7 +141,7 @@ public void execute() throws MojoExecutionException {
task.setOutputBaseDirectory(outputDirectory);
task.setFieldPackage(fieldPackage);
task.setUtcTimestampPrecision(utcTimestampPrecision);
- task.setOverwrite(true);
+ task.setOverwrite(overwrite);
task.setOrderedFields(orderedFields);
task.setDecimalGenerated(decimal);
generator.generate(task);
@@ -326,4 +324,18 @@ public String getUtcTimestampPrecision() {
public void setUtcTimestampPrecision(String utcTimestampPrecision) {
this.utcTimestampPrecision = utcTimestampPrecision;
}
+
+ /**
+ * @return whether the code generator should overwrite existing files with the same name
+ */
+ public boolean isOverwrite() {
+ return overwrite;
+ }
+
+ /**
+ * @param overwrite sets whether the code generator should overwrite existing files with the same name
+ */
+ public void setOverwrite(boolean overwrite) {
+ this.overwrite = overwrite;
+ }
}
diff --git a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java
index b4e90f9b00..615e639661 100644
--- a/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java
+++ b/quickfixj-codegenerator/src/main/java/org/quickfixj/codegenerator/MessageCodeGenerator.java
@@ -138,7 +138,7 @@ private void generateFieldClasses(Task task) throws ParserConfigurationException
Transformer transformer = createTransformer(task, "Fields.xsl");
for (String fieldName : fieldNames) {
String outputFile = outputDirectory + fieldName + ".java";
- if (!new File(outputFile).exists()) {
+ 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);
@@ -305,13 +305,7 @@ private void generateCodeFile(Task task, Document document, Map
if (!task.isOverwrite()) {
return;
}
- if (outputFile.lastModified() > task.getSpecificationLastModified()) {
- logDebug("Skipping file " + outputFile.getName());
- return;
- }
}
- logDebug("spec has mod " + task.getSpecificationLastModified() +
- " output has mod " + outputFile.lastModified());
DOMSource source = new DOMSource(document);
FileOutputStream fos = new FileOutputStream(outputFile);
diff --git a/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl b/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl
index 8cfe01c884..d7eeae25cd 100644
--- a/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl
+++ b/quickfixj-codegenerator/src/main/resources/org/quickfixj/codegenerator/Fields.xsl
@@ -75,21 +75,21 @@ import java.time.LocalTime;
public class extends Field {
- static final long serialVersionUID = ;
+ static final long serialVersionUID = ;
- public static final int FIELD = ;
-
- public () {
- super( );
- }
+ public static final int FIELD = ;
+
+ public () {
+ super( );
+ }
- public ( data) {
- super( , data, true );
- }
+ public ( data) {
+ super( , data, true );
+ }
public (double data) {
- super( , new (data));
- }
+ super( , new (data));
+ }
@Override
@@ -112,6 +112,8 @@ public class extends LocalTime
LocalDate
LocalDate
+ String
+ String
boolean
double
@@ -140,6 +142,8 @@ public class extends UtcTimeOnly
UtcDateOnly
UtcDateOnly
+ String
+ String
Boolean
Double
@@ -177,32 +181,32 @@ public class extends
- public static final String = " ";
-
- public static final String = " ";
-
- public static final String = " ";
-
+ public static final String = " ";
+
+ public static final String = " ";
+
+ public static final String = " ";
+
public static final boolean = ;
-
- public static final int = ;
-
- public static final int = ;
-
- public static final String = " ";
-
- public static final String = " ";
-
- public static final char = ' ';
-
+
+ public static final int = ;
+
+ public static final int = ;
+
+ public static final String = " ";
+
+ public static final String = " ";
+
+ public static final char = ' ';
+
- public static final String OPTION = "OPT";
+ public static final String OPTION = "OPT";
- public static final String FUTURE = "FUT";
+ public static final String FUTURE = "FUT";
diff --git a/quickfixj-codegenerator/src/test/java/org/quickfixj/codegenerator/OverwriteTest.java b/quickfixj-codegenerator/src/test/java/org/quickfixj/codegenerator/OverwriteTest.java
new file mode 100644
index 0000000000..a5358b071f
--- /dev/null
+++ b/quickfixj-codegenerator/src/test/java/org/quickfixj/codegenerator/OverwriteTest.java
@@ -0,0 +1,135 @@
+package org.quickfixj.codegenerator;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Scanner;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.junit.Before;
+import org.junit.Test;
+import org.apache.commons.io.FileUtils;
+
+public class OverwriteTest {
+
+ private File outputDirectory = new File ("./target/test-output/");
+ private File dictDirectory = new File ("./src/test/resources");
+ private File schemaDirectory = new File ("./src/main/resources/org/quickfixj/codegenerator");
+ private String fieldPackage = "quickfix.field";
+ private String utcTimestampPrecision = null;
+ private boolean orderedFields = true;
+ private boolean decimal = true;
+ private MessageCodeGenerator generator;
+
+ @Before
+ public void setup() throws IOException {
+ if (outputDirectory.exists()){
+ FileUtils.cleanDirectory(outputDirectory);
+ } else {
+ outputDirectory.mkdirs();
+ }
+ generator = new MessageCodeGenerator();
+ System.out.println("Successfully created an instance of the QuickFIX source generator");
+ }
+
+ @Test
+ public void testFieldOverwrittenWhenOverwriteTrue() {
+
+ boolean overwrite = true;
+
+ MessageCodeGenerator.Task task = new MessageCodeGenerator.Task();
+ System.out.println("Initialising code generator task");
+
+ try {
+ String packaging = "quickfix.fix41";
+ File fix41Dictfile = new File( dictDirectory, "FIX41.xml" );
+ generate(generator, task, fix41Dictfile, packaging, overwrite);
+
+ packaging = "quickfix.fix42"; // this does not affect this test
+ File fix42Dictfile = new File( dictDirectory, "FIX42.xml" );
+ generate(generator, task, fix42Dictfile, packaging, overwrite);
+ } catch (MojoExecutionException e) {
+ e.printStackTrace();
+ fail();
+ }
+
+ String expectedFilePath = outputDirectory.getAbsolutePath() + "/quickfix/field/AllocShares.java";
+ File file = new File(expectedFilePath);
+ assertTrue(file.exists());
+
+ boolean isAllocSharesDecimal = isAllocSharesDecimal(file);
+ assertTrue(isAllocSharesDecimal);
+ }
+
+ @Test
+ public void testFieldNotOverwrittenWhenOverwriteFalse() {
+
+ boolean overwrite = false;
+
+ MessageCodeGenerator.Task task = new MessageCodeGenerator.Task();
+ System.out.println("Initialising code generator task");
+
+ try {
+ String packaging = "quickfix.fix41";
+ File fix41Dictfile = new File( dictDirectory, "FIX41.xml" );
+ generate(generator, task, fix41Dictfile, packaging, overwrite);
+
+ packaging = "quickfix.fix42"; // this does not affect this test
+ File fix42Dictfile = new File( dictDirectory, "FIX42.xml" );
+ generate(generator, task, fix42Dictfile, packaging, overwrite);
+ } catch (MojoExecutionException e) {
+ e.printStackTrace();
+ fail();
+ }
+
+ String expectedFilePath = outputDirectory.getAbsolutePath() + "/quickfix/field/AllocShares.java";
+ File file = new File(expectedFilePath);
+ assertTrue(file.exists());
+
+ boolean isAllocSharesDecimal = isAllocSharesDecimal(file);
+ assertFalse(isAllocSharesDecimal);
+ }
+
+ private void generate(MessageCodeGenerator generator, MessageCodeGenerator.Task task, File dictfile,
+ String packaging, boolean overwrite) throws MojoExecutionException {
+ if (dictfile != null && dictfile.exists()) {
+ task.setSpecification(dictfile);
+ } else {
+ throw new MojoExecutionException("File could not be found or was NULL!");
+ }
+
+ System.out.println("Processing " + dictfile);
+
+ task.setName(dictfile.getName());
+ task.setTransformDirectory(schemaDirectory);
+ task.setMessagePackage(packaging);
+ task.setOutputBaseDirectory(outputDirectory);
+ task.setFieldPackage(fieldPackage);
+ task.setUtcTimestampPrecision(utcTimestampPrecision);
+ task.setOverwrite(overwrite);
+ task.setOrderedFields(orderedFields);
+ task.setDecimalGenerated(decimal);
+ generator.generate(task);
+ }
+
+ private boolean isAllocSharesDecimal(File file) {
+ boolean isAllocSharesDecimal = false;
+ try (Scanner scanner = new Scanner(file)) {
+ //now read the file line by line...
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if(line.contains("AllocShares extends DecimalField")) {
+ isAllocSharesDecimal = true;
+ break;
+ }
+ }
+ } catch(FileNotFoundException e) {
+ e.printStackTrace();
+ fail();
+ }
+ return isAllocSharesDecimal;
+ }
+
+}
diff --git a/quickfixj-codegenerator/src/test/resources/FIX41.xml b/quickfixj-codegenerator/src/test/resources/FIX41.xml
new file mode 100644
index 0000000000..4a328f079b
--- /dev/null
+++ b/quickfixj-codegenerator/src/test/resources/FIX41.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-codegenerator/src/test/resources/FIX42.xml b/quickfixj-codegenerator/src/test/resources/FIX42.xml
new file mode 100644
index 0000000000..d48a87328f
--- /dev/null
+++ b/quickfixj-codegenerator/src/test/resources/FIX42.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickfixj-core/pom.xml b/quickfixj-core/pom.xml
index cd7c61e943..9329bfa0f4 100644
--- a/quickfixj-core/pom.xml
+++ b/quickfixj-core/pom.xml
@@ -1,490 +1,368 @@
- 4.0.0
-
- org.quickfixj
- quickfixj-parent
- 3.0.0-SNAPSHOT
-
+ 4.0.0
+
+ org.quickfixj
+ quickfixj-parent
+ 3.0.0-SNAPSHOT
+
- quickfixj-core
- bundle
+ quickfixj-core
+ bundle
- QuickFIX/J Core engine
- The core QuickFIX/J engine
- http://www.quickfixj.org
+ QuickFIX/J Core engine
+ The core QuickFIX/J engine
+ http://www.quickfixj.org
-
- **/AcceptanceTestSuite.java
- org.quickfixj.Version
-
+
+ **/AcceptanceTestSuite.java
+ org.quickfixj.Version
+
-
-
- junit
- junit
- ${junit.version}
- test
-
-
- org.mockito
- mockito-core
- 4.11.0
- test
-
-
- org.hamcrest
- hamcrest
- 2.2
- test
-
-
- hsqldb
- hsqldb
- 1.8.0.10
- test
-
-
- tyrex
- tyrex
- 1.0.1
- test
-
-
- org.slf4j
- slf4j-jdk14
- ${slf4j.version}
- test
-
+
+
+ org.quickfixj
+ quickfixj-base
+ ${project.version}
+
+
+ org.quickfixj
+ quickfixj-messages-fixlatest
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix50sp2
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix50sp1
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix50
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix44
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix43
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix42
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix41
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fix40
+ ${project.version}
+ test
+
+
+ org.quickfixj
+ quickfixj-messages-fixt11
+ ${project.version}
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ org.hamcrest
+ hamcrest
+ test
+
+
+ hsqldb
+ hsqldb
+ 1.8.0.10
+ test
+
+
+ tyrex
+ tyrex
+ 1.0.1
+ test
+
+
+ org.slf4j
+ slf4j-jdk14
+ ${slf4j.version}
+ test
+
+
+ org.apache.mina
+ mina-core
+
+
+ org.slf4j
+ slf4j-api
+ ${slf4j.version}
+
+
+ com.cloudhopper.proxool
+ proxool
+ 0.9.1
+ true
+
+
+
+ avalon-framework
+ avalon-framework-api
+
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ com.cloudhopper.proxool
+ proxool-cglib
+ 0.9.1
+ true
+
+
+
+ avalon-framework
+ avalon-framework-api
+
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.slf4j
+ jcl-over-slf4j
+ ${slf4j.version}
+ runtime
+ true
+
+
+ com.sleepycat
+ je
+ 18.3.12
+ true
+
+
-
- org.apache.mina
- mina-core
- 2.1.6
-
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
-
+
+
+
-
- com.cloudhopper.proxool
- proxool
- 0.9.1
- true
-
-
-
- avalon-framework
- avalon-framework-api
-
-
-
- commons-logging
- commons-logging
-
-
-
-
- com.cloudhopper.proxool
- proxool-cglib
- 0.9.1
- true
-
-
-
- avalon-framework
- avalon-framework-api
-
-
-
- commons-logging
- commons-logging
-
-
-
-
-
- org.slf4j
- jcl-over-slf4j
- ${slf4j.version}
- runtime
- true
-
-
- com.sleepycat
- je
- 18.3.12
- true
-
-
+
+
+ src/test/resources
+
+
+ src/main/resources
+
+
-
-
-
- ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix50sp2/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix50sp1/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix43/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix42/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix41/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources
-
-
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ quickfix/**
+ org/**
+ quickfix/field/*
+ quickfix/field/converter/*
+ FIX*.xml
+
+
+
+ quickfix/fix*/**
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+
+
+ quickfix/**
+ org/**
+ quickfix/field/*
+ quickfix/field/converter/*
+ FIX*.xml
+
+
+
+ quickfix/fix*/**
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+ true
+ 8
+ src/main/java:${project.build.directory}/generated-sources
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+ quickfix,quickfix.*,org.quickfixj,org.quickfixj.*
+
+
+
+
+
+
-
-
- src/test/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix50sp2/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix50sp1/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix43/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix42/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix41/src/main/resources
-
-
- ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources
-
-
- src/main/resources
-
-
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-report-plugin
+ ${maven-surefire-plugin-version}
+
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-jxr-plugin
+ 3.3.0
+
+
+
-
-
- org.quickfixj
- quickfixj-codegenerator
- ${project.version}
-
-
- fixt11
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fixt11/src/main/resources/FIXT11.xml
- quickfix.fixt11
- quickfix.field
- ${generator.decimal}
-
-
-
- fix50sp2
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix50sp2/src/main/resources/FIX50SP2.modified.xml
- quickfix.fix50sp2
- quickfix.field
- ${generator.decimal}
-
-
-
- fix50sp1
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix50sp1/src/main/resources/FIX50SP1.modified.xml
- quickfix.fix50sp1
- quickfix.field
- ${generator.decimal}
-
-
-
- fix50
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix50/src/main/resources/FIX50.xml
- quickfix.fix50
- quickfix.field
- ${generator.decimal}
-
-
-
- fix44
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix44/src/main/resources/FIX44.modified.xml
- quickfix.fix44
- quickfix.field
- ${generator.decimal}
-
-
-
- fix43
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix43/src/main/resources/FIX43.xml
- quickfix.fix43
- quickfix.field
- ${generator.decimal}
-
-
-
- fix42
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix42/src/main/resources/FIX42.xml
- quickfix.fix42
- quickfix.field
- ${generator.decimal}
-
-
-
- fix41
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix41/src/main/resources/FIX41.xml
- quickfix.fix41
- quickfix.field
- ${generator.decimal}
-
-
-
- fix40
-
- generate
-
-
- ../quickfixj-messages/quickfixj-messages-fix40/src/main/resources/FIX40.xml
- quickfix.fix40
- quickfix.field
- ${generator.decimal}
-
-
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
- ${maven-jar-plugin-version}
-
-
- quickfix/**
- org/**
- quickfix/field/converter/*
- FIX*.xml
-
-
- quickfix/field/*
- quickfix/fix*/**
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
-
-
- quickfix/**
- org/**
- quickfix/field/converter/*
- FIX*.xml
-
-
- quickfix/field/*
- quickfix/fix*/**
-
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- ${maven-javadoc-plugin-version}
-
-
- attach-javadocs
-
- jar
-
-
- none
- 3g
- false
-
- src/main/java
-
-
-
-
-
- org.apache.felix
- maven-bundle-plugin
-
-
- quickfix,quickfix.field.*,quickfix.mina.*,org.quickfixj.*
-
-
- quickfix.fix40;resolution:=optional,
- quickfix.fix41;resolution:=optional,
- quickfix.fix42;resolution:=optional,
- quickfix.fix43;resolution:=optional,
- quickfix.fix44;resolution:=optional,
- quickfix.fix50;resolution:=optional,
- quickfix.fix50sp1;resolution:=optional,
- quickfix.fix50sp2;resolution:=optional,
- quickfix.fixt11;resolution:=optional,
-
- quickfix,quickfix.field,*
-
-
-
-
-
-
-
-
-
-
-
- maven-surefire-report-plugin
- ${maven-surefire-plugin-version}
-
-
- true
-
-
-
- maven-jxr-plugin
- 3.3.0
-
-
-
-
-
-
-
- skipAT
-
-
- skipAT
- true
-
-
-
-
-
-
-
-
- surefire-java8
-
- 1.8
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- -Xmx512m -Djava.net.preferIPv4Stack=true
-
- false
-
- **/*Test.java
- ${acceptance.tests}
-
-
- **/*ForTest.java
- **/Abstract*Test.java
- **/AcceptanceTestSuite$*
-
-
- 5
- 60000
- 5
- false
-
-
-
-
-
-
-
-
- surefire
-
- [1.9,)
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- -Xmx512m -Djava.net.preferIPv4Stack=true
- --add-opens java.base/java.lang=ALL-UNNAMED
-
- false
-
- **/*Test.java
- ${acceptance.tests}
-
-
- **/*ForTest.java
- **/Abstract*Test.java
- **/AcceptanceTestSuite$*
-
-
- 5
- 60000
- 5
- false
-
-
-
-
-
-
-
+
+
+
+ skipAT
+
+
+ skipAT
+ true
+
+
+
+
+
+
+
+
+ surefire-java8
+
+ 1.8
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ -Xmx2g -Djava.net.preferIPv4Stack=true
+
+ false
+
+ **/*Test.java
+ ${acceptance.tests}
+
+
+ **/*ForTest.java
+ **/Abstract*Test.java
+ **/AcceptanceTestSuite$*
+
+
+ 5
+ 60000
+ 5
+ false
+
+
+
+
+
+
+
+
+ surefire
+
+ [1.9,)
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ -Xmx2g -Djava.net.preferIPv4Stack=true
+ --add-opens java.base/java.lang=ALL-UNNAMED
+
+ false
+
+ **/*Test.java
+ ${acceptance.tests}
+
+
+ **/*ForTest.java
+ **/Abstract*Test.java
+ **/AcceptanceTestSuite$*
+
+
+ 5
+ 60000
+ 5
+ false
+
+
+
+
+
+
+
diff --git a/quickfixj-core/readme.md b/quickfixj-core/readme.md
new file mode 100644
index 0000000000..e1a31e128c
--- /dev/null
+++ b/quickfixj-core/readme.md
@@ -0,0 +1,13 @@
+# quickfixj-core
+
+This module builds the QuickFIX/J FIX "engine" including supporting capabilities.
+
+The core engine depends on ```quickfixj-base```. A small number of derived Fields are provided by ```quickfixj-base``.
+
+ This module has **test** dependency on generated message classes. The distinction of *test* dependency is significant as it allows easier [customisation](../customising-quickfixj.md) of QuickFIX/J deployments.
+
+## Developing quickfixj-core
+
+To develop this module build ```quickfixj-base``` and ```quickfixj-messages``` first, or build the whole project.
+
+Full ```quickfixj-messages``` build times can be long. See [the ```quickfixj-messages``` readme](../quickfixj-messages/readme.md) for how to do a minimal development build of the messages. Once these dependencies are built then ```quickfixj-core``` can be built and tested independently for higher productivity.
diff --git a/quickfixj-core/src/main/doc/usermanual/installation.html b/quickfixj-core/src/main/doc/usermanual/installation.html
index f05bea37a7..58c88bad9a 100644
--- a/quickfixj-core/src/main/doc/usermanual/installation.html
+++ b/quickfixj-core/src/main/doc/usermanual/installation.html
@@ -13,39 +13,77 @@ QuickFIX/J User Manual
Installation and Building the Code.
Runtime Dependencies
Java Virtual Machine:
-JVM compatible with Oracle JRE Java 1.7.x or higher.
+JVM compatible with JRE Java 1.8.x or higher.
Required run-time libraries:
+QuickFIX/J has "base" and "core" libraries for the FIX "Engine" and one or more Message libraries are required at runtime.
+
+The required Message libraries depend on the FIX Protocol Version. A QuickFIX/J process can support more than one FIX Protocol version at runtime, using different Session configurations.
+
+Please note that customised versions of the FIX message artefacts can be built to meet specific Rules of Engagement. Please refer to the QuickFIX/J github repository for details .
+
-(Note: The actual JAR files may have version numbers in them.)
+(Note: The actual JAR files have semantic version numbers in the name. For example : quickfixj-messages-fixlatest-3.0.0.jar)
-
- Library
- Description
-
-
- The QFJ core JAR and message JARs.
-
- quickfixj-core.jar
- quickfixj-msg-fix40.jar
- quickfixj-msg-fix41.jar
- quickfixj-msg-fix42.jar
- quickfixj-msg-fix43.jar
- quickfixj-msg-fix44.jar
- quickfixj-msg-fix50.jar
- quickfixj-msg-fix50sp1.jar
- quickfixj-msg-fix50sp2.jar
-
- or
-
- quickfixj-all.jar (includes core and message JARs)
-
-
- QFJ runtime libraries
-
+
+ Library
+ Description
+
+
+ The QuickFIX/J base and core JARs and message JARs.
+
+ quickfixj-base-{VERSION}.jar
+ quickfixj-core-{VERSION}.jar
+
+
+ QuickFIX/J fundamental libraries
+
+
+ With one or more message JAR for FIX versions before FIX 5.0
+
+ quickfixj-messages-fix40-{VERSION}.jar
+ quickfixj-messages-fix41-{VERSION}.jar
+ quickfixj-messages-fix42-{VERSION}.jar
+ quickfixj-messages-fix43-{VERSION}.jar
+ quickfixj-messages-fix44-{VERSION}.jar
+
+ AND/OR
+
+
+
+ The FIX Transport Layer JAR
+
+ quickfixj-messages-fixt11-{VERSION}.jar
+
+
+
+
+
+ With one or more FIX Application Layer message JAR for FIX versions 5.0 and later
+
+ quickfixj-messages-fix50-{VERSION}.jar
+ quickfixj-messages-fix50sp1-{VERSION}.jar
+ quickfixj-messages-fix50sp2-{VERSION}.jar
+ quickfixj-messages-fixlatest-{VERSION}.jar
+
+
+
+
+
+ Message Libraries required at runtime
+
+ OR
+
+
+
+ quickfixj-all-{VERSION}.jar (includes core, base and message JARs)
+
+
+ JAR including base, core and message JARs
+
@@ -108,12 +146,11 @@ Optional run-time libraries:
Building QuickFIX/J
These instructions are for developers who don't want
- to use the prebuilt binaries or are intending to modify and rebuild the QuickFIX/J
+ to use the pre-built binaries or are intending to modify and rebuild the QuickFIX/J
code. If you are building the code from the command line you'll need
- to download and install Maven (version 3.2.5 or newer). If you are building from
- an IDE, Maven is usually included.
+ to download and install Maven (version 3.5.0 or newer).
-Building from source requires Java 7+.
+Building from source requires Java 8 or later.
@@ -152,6 +189,7 @@ Command-line Switches
For example, in order to generate fields with BigDecimals and skip acceptance tests:
mvn test -Dgenerator.decimal=true -DskipAT=true
+Find further details in the project readme
IDE support:
When the project is first created, it will not have the generated message classes
and compile errors will occur! Best is to compile once on the command line before importing
@@ -163,52 +201,62 @@
Maven Integration:
If you are using the Maven build system, you can reference
the pre-built QuickFIX/J libraries hosted at the Central Repository
repository.
- Add the following to your dependencies section, with appropriate modifications based on
- the logging subsystem you choose:
-
+You need only include message libraries for the FIX protocol version that you require.
+Note : the quickfixj-messages-fixt11 jar is required for FIX versions 5.0 and later.
+The following example shows maven dependencies , any SLF4J compatible logging implementation can be used:
+
<!-- QuickFIX/J dependencies -->
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-core</artifactId>
- <version>2.0.0</version>
+ <version>3.0.0</version>
+</dependency>
+<dependency>
+ <groupId>org.quickfixj</groupId>
+ <artifactId>quickfixj-base</artifactId>
+ <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix40</artifactId>
- <version>2.0.0</version>
+ <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix41</artifactId>
- <version>2.0.0</version>
+ <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix42</artifactId>
- <version>2.0.0</version>
+ <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix43</artifactId>
- <version>2.0.0</version>
+ <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.quickfixj</groupId>
<artifactId>quickfixj-messages-fix44</artifactId>
- <version>2.0.0</version>
+ <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
- <artifactId>slf4j-log4j12</artifactId>
- <version>1.7.22</version>
+ <artifactId>slf4j-api</artifactId>
+ <version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- <version>1.7.22</version>
-</dependency>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>2.0.6/version>
+</dependency>
+
+
+Please refer to the QuickFIX/J github repository for more details about working with QuickFIX/J.
+
Generating the database for JDBC based store and log
Everything needed to generate your database is in the src/main/resources/config/sql subdirectories.
For MySQL, there are the script and batch files create_mysql.sh
and create_mysql.bat
.
@@ -219,7 +267,7 @@
Generating the database for JDBC based store and log<
'root' to another user. To add a password, use pass the -p option after the
username. Similar scripts are provided for MSSQL, PostgreSQL and Oracle.
-
+
Oracle treats empty strings as null values. Null values are not allowed for primary key
fields. The fields used in the primary keys are:
diff --git a/quickfixj-core/src/main/doc/usermanual/support.html b/quickfixj-core/src/main/doc/usermanual/support.html
index caec340aa2..0fc87ec45c 100644
--- a/quickfixj-core/src/main/doc/usermanual/support.html
+++ b/quickfixj-core/src/main/doc/usermanual/support.html
@@ -13,7 +13,7 @@
QuickFIX/J User Manual
QuickFIX/J Support
Mailing Lists
-QuickFIX/J issues can be discussed on the mailing
+QuickFIX/J issues can be discussed on the mailing
lists hosts at the SourceForge
project site.
Web Site
@@ -21,9 +21,11 @@ Web Site
site.
More information about the original C++ QuickFIX can be found at the quickfixengine.org web
site.
+Git Hub
+Further detailed documents and source code can be found on github
FIX Protocol
-More information about the FIX protocol can be found at the FIX
- Protocol Limited web site .
+More information about the FIX protocol can be found at the FIX
+ Trading Community web site .