43 Commits
Author SHA1 Message Date
Enrique Zamudio efa97a023f Version 1.7.0 2013-05-07 15:39:46 -05:00
Enrique Zamudio be54b9f38e Fix #9 - Binary parsing with CustomBinaryField for LLBIN/LLLBIN fields 2013-05-07 15:37:49 -05:00
Enrique Zamudio b164af83c4 runtime slf4j 2013-05-07 15:37:32 -05:00
Enrique Zamudio eb59dcc202 Consider CustomBinaryField codec for LLBIN/LLLBIN ref #9 2013-05-07 15:37:11 -05:00
Enrique Zamudio 3758a48d90 More tests for #9 2013-05-07 15:36:37 -05:00
Enrique Zamudio a591b0269f ignore workspace 2013-05-07 14:12:49 -05:00
Enrique Zamudio 376dcdec48 New IntelliJ project format 2013-05-07 14:12:31 -05:00
Enrique Zamudio f29d8f7ff4 Document Bcd methods 2013-05-07 13:45:37 -05:00
Enrique Zamudio e81116d0c6 New interface for custom binary field codecs 2013-05-07 13:45:20 -05:00
Enrique Zamudio ba56dc321a Tests for #9 2013-05-07 13:44:59 -05:00
Enrique Zamudio 449e321645 Work on #9 - Numeric codecs for LLBIN fields 2013-05-07 13:44:27 -05:00
Enrique Zamudio 0b50ae3f1d ignore "out" dir used by IntelliJ 2013-05-07 12:30:41 -05:00
Enrique Zamudio 4a1f165c0b improve use of generics 2013-05-07 12:30:33 -05:00
Enrique Zamudio b1884c29d1 Seal helper class 2013-05-07 12:30:19 -05:00
Enrique Zamudio 04ba54e522 Work on #9 - group BCD routines in a convenience class 2013-04-23 11:42:39 -05:00
Enrique Zamudio f1055fddce update version 2013-04-23 11:23:07 -05:00
Enrique Zamudio ab9bded8a6 Update test code 2013-04-23 11:22:48 -05:00
Enrique Zamudio cc5918dd50 Fix #10 - improve use of type parameters instead of suppressing warnings 2013-04-23 11:22:21 -05:00
Enrique Zamudio 0221f8881a suppress unchecked warning 2013-04-23 11:17:13 -05:00
Enrique Zamudio d297625dfa suppress unchecked warning 2013-04-23 11:16:56 -05:00
Enrique Zamudio 37c6a9ce9e Tests for #7 2013-04-23 10:51:27 -05:00
Enrique Zamudio e91a272f73 Fix #7 - add new setter that only updates existing values 2013-04-23 10:50:32 -05:00
Enrique Zamudio 8dd759f92f Fix #8 - change parsing maps visibility to protected 2013-04-23 10:30:00 -05:00
Enrique Zamudio 6be7738166 Generify call/apply methods 2013-04-15 17:28:22 -05:00
Enrique Zamudio 602974ad82 Fix #6 class loader handling, and make createFromXXX invariant 2013-01-24 17:23:49 -06:00
Enrique Zamudio 3f41a9179d 1.6.1 snapshot 2013-01-24 17:23:22 -06:00
Enrique Zamudio 175c3c1dfe Set version to 1.6.0 2013-01-18 13:41:29 -06:00
Enrique Zamudio 41507d8b5c Next version will be 1.6 due to API changes 2013-01-18 13:33:49 -06:00
Enrique Zamudio d3e34d760a fix workspace 2013-01-18 13:33:38 -06:00
Enrique Zamudio 92af1156cc Use new API 2013-01-18 13:33:06 -06:00
Enrique Zamudio 4022ab3ad8 Encode field number in variable fields to tell them apart 2013-01-18 13:32:27 -06:00
Enrique Zamudio 0d2e5409b4 Fix parsing of binary messages 2013-01-18 13:31:32 -06:00
Enrique Zamudio 4d02af0a76 Use new HexCodec.hexEncode 2013-01-18 13:31:17 -06:00
Enrique Zamudio 92c6b6d4f9 Change hexEncode to allow encoding of array region 2013-01-18 13:30:31 -06:00
Enrique Zamudio 913d74a133 Fix #5 - Add field number to parse error messages 2013-01-18 13:29:39 -06:00
Enrique Zamudio d570eb4885 Fix parsing of text messages with binary bitmaps - see #4 2013-01-18 12:04:43 -06:00
Enrique Zamudio a3a7b53fa0 write length header directly to ByteBuffer 2013-01-18 12:04:17 -06:00
Enrique Zamudio 6abbeb93bb Fix #4 (which wasn't really a bug but uncovered a couple of them) 2013-01-18 12:03:32 -06:00
Enrique Zamudio 32fe3f20f4 set version number 2012-12-26 16:03:26 -06:00
Enrique Zamudio 2e5911f261 work on #3 - catch IOOBE 2012-12-26 16:02:38 -06:00
Enrique Zamudio 778501b2d6 Work on #3 for ALPHA fields 2012-12-26 15:34:09 -06:00
Enrique Zamudio d549b00563 update version number 2012-12-26 15:33:57 -06:00
Enrique Zamudio 8c1abc6b88 update version number 2012-12-26 15:33:44 -06:00
42 changed files with 1250 additions and 347 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
target
.idea
.gradle
build
out
*.iws
+9 -4
View File
@@ -1,7 +1,7 @@
apply plugin:'java'
apply plugin: 'maven'
defaultTasks 'build'
version='1.5.7'
version='1.7.0'
description='Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible.'
sourceCompatibility=6
targetCompatibility=6
@@ -17,9 +17,9 @@ configurations {
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.1'
testCompile 'junit:junit:4.10'
testRuntime 'org.slf4j:slf4j-simple:1.7.1'
compile 'org.slf4j:slf4j-api:1.7.5'
testCompile 'junit:junit:4.11'
testRuntime 'org.slf4j:slf4j-simple:1.7.5'
}
tasks.javadoc.options.use=true
@@ -34,6 +34,11 @@ task sourcesJar(type:Jar) {
classifier='sources'
}
tasks.withType(Compile) { Compile compile ->
compile.options.debug = true
compile.options.compilerArgs = ['-Xlint:all']
}
artifacts {
archives javadocJar
archives sourcesJar
+2 -2
View File
@@ -3,10 +3,10 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
+299
View File
@@ -0,0 +1,299 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="?*.properties" />
<entry name="?*.xml" />
<entry name="?*.gif" />
<entry name="?*.png" />
<entry name="?*.jpeg" />
<entry name="?*.jpg" />
<entry name="?*.html" />
<entry name="?*.dtd" />
<entry name="?*.tld" />
<entry name="?*.ftl" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
<component name="CopyrightManager" default="">
<module2copyright />
</component>
<component name="DependencyValidationManager">
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</component>
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="GradleLocalSettings">
<option name="availableTasks">
<set>
<GradleTaskDescriptor>
<option name="description" value="Assembles the outputs of this project." />
<option name="name" value="assemble" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Assembles and tests this project." />
<option name="name" value="build" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Assembles and tests this project and all projects that depend on it." />
<option name="name" value="buildDependents" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Assembles and tests this project and all projects it depends on." />
<option name="name" value="buildNeeded" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Runs all checks." />
<option name="name" value="check" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Assembles the main classes." />
<option name="name" value="classes" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Deletes the build directory." />
<option name="name" value="clean" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Compiles the main Java source." />
<option name="name" value="compileJava" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Compiles the test Java source." />
<option name="name" value="compileTestJava" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Assembles a jar archive containing the main classes." />
<option name="name" value="jar" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Generates Javadoc API documentation for the main source code." />
<option name="name" value="javadoc" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Processes the main resources." />
<option name="name" value="processResources" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Processes the test resources." />
<option name="name" value="processTestResources" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Runs the unit tests." />
<option name="name" value="test" />
</GradleTaskDescriptor>
<GradleTaskDescriptor>
<option name="description" value="Assembles the test classes." />
<option name="name" value="testClasses" />
</GradleTaskDescriptor>
</set>
</option>
</component>
<component name="GradleSettings">
<option name="gradleHome" value="$PROJECT_DIR$/../../../gradle-1.4" />
<option name="preferLocalInstallationToWrapper" value="true" />
</component>
<component name="GradleUISettings2">
<setting name="root" />
</component>
<component name="IdProvider" IDEtalkID="E31FCDAA3C23FC998F967D8CCAB8763A" />
<component name="InspectionProjectProfileManager">
<profiles>
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</profiles>
<option name="PROJECT_PROFILE" value="Project Default" />
<option name="USE_PROJECT_PROFILE" value="true" />
<version value="1.0" />
</component>
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" />
<option name="OPTION_SCOPE" value="protected" />
<option name="OPTION_HIERARCHY" value="true" />
<option name="OPTION_NAVIGATOR" value="true" />
<option name="OPTION_INDEX" value="true" />
<option name="OPTION_SEPARATE_INDEX" value="true" />
<option name="OPTION_DOCUMENT_TAG_USE" value="false" />
<option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
<option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
<option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
<option name="OPTION_DEPRECATED_LIST" value="true" />
<option name="OTHER_OPTIONS" value="" />
<option name="HEAP_SIZE" />
<option name="LOCALE" />
<option name="OPEN_IN_BROWSER" value="true" />
</component>
<component name="ModuleEditorState">
<option name="LAST_EDITED_MODULE_NAME" />
<option name="LAST_EDITED_TAB_NAME" />
</component>
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
<component name="ProjectDictionaryState">
<dictionary name="ezamudio" />
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/j8583.iml" filepath="$PROJECT_DIR$/j8583.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="Java 6" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="SvnConfiguration" maxAnnotateRevisions="500" myUseAcceleration="nothing" myAutoUpdateAfterCommit="false" cleanupOnStartRun="false">
<option name="USER" value="" />
<option name="PASSWORD" value="" />
<option name="mySSHConnectionTimeout" value="30000" />
<option name="mySSHReadTimeout" value="30000" />
<option name="LAST_MERGED_REVISION" />
<option name="MERGE_DRY_RUN" value="false" />
<option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
<option name="UPDATE_LOCK_ON_DEMAND" value="false" />
<option name="IGNORE_SPACES_IN_MERGE" value="false" />
<option name="DETECT_NESTED_COPIES" value="true" />
<option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" />
<option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
<option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
<option name="FORCE_UPDATE" value="false" />
<option name="IGNORE_EXTERNALS" value="false" />
<myIsUseDefaultProxy>false</myIsUseDefaultProxy>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+1 -1
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.sf.j8583</groupId>
<artifactId>j8583</artifactId>
<version>1.5.6</version>
<version>1.5.8-SNAPSHOT</version>
<packaging>jar</packaging>
<name>j8583</name>
<description>Java implementation of the ISO 8583 protocol, focused on making the creation, edition and reading of ISO8583 messages as simple and flexible as possible.</description>
@@ -0,0 +1,17 @@
package com.solab.iso8583;
/**
* An extension of the CustomField interface, useful for binary fields.
* CustomBinaryField encoders can return null for the two CustomField
* methods IF they are only used with binary messages.
*
* @author Enrique Zamudio
* Date: 07/05/13 13:04
*/
public interface CustomBinaryField<T> extends CustomField<T> {
public T decodeBinaryField(byte[] value, int offset, int length);
public byte[] encodeBinaryField(T value);
}
+25 -12
View File
@@ -205,6 +205,25 @@ public class IsoMessage {
return this;
}
/** A convenience method to set new values in fields that already contain values.
* The field's type, length and custom encoder are taken from the current value.
* This method can only be used with fields that have been previously set,
* usually from a template in the MessageFactory.
* @param index The field's index
* @param value The new value to be set in that field.
* @return The message itself.
* @throws IllegalArgumentException if there is no current field at the specified index. */
public <T> IsoMessage updateValue(int index, T value) {
IsoValue<T> current = getField(index);
if (current == null) {
throw new IllegalArgumentException("Value-only field setter can only be used on existing fields");
} else {
setValue(index, value, current.getEncoder(), current.getType(), current.getLength());
getField(index).setCharacterEncoding(current.getCharacterEncoding());
}
return this;
}
/** Returns true is the message has a value in the specified field.
* @param idx The field number. */
public boolean hasField(int idx) {
@@ -270,22 +289,16 @@ public class IsoMessage {
if (etx > -1) {
l++;
}
byte[] bbuf = new byte[lengthBytes];
int pos = 0;
if (lengthBytes == 4) {
bbuf[0] = (byte)((l & 0xff000000) >> 24);
pos++;
buf.put((byte)((l & 0xff000000) >> 24));
}
if (lengthBytes > 2) {
bbuf[pos] = (byte)((l & 0xff0000) >> 16);
pos++;
buf.put((byte)((l & 0xff0000) >> 16));
}
if (lengthBytes > 1) {
bbuf[pos] = (byte)((l & 0xff00) >> 8);
pos++;
buf.put((byte)((l & 0xff00) >> 8));
}
bbuf[pos] = (byte)(l & 0xff);
buf.put(bbuf);
buf.put((byte)(l & 0xff));
}
buf.put(data);
//ETX
@@ -383,7 +396,7 @@ public class IsoMessage {
//These are for Groovy compat
/** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
public void putAt(int i, IsoValue<?> v) {
public <T> void putAt(int i, IsoValue<T> v) {
setField(i, v);
}
/** Returns the IsoValue in the specified field, just like {@link #getField(int)}. */
@@ -393,7 +406,7 @@ public class IsoMessage {
//These are for Scala compat
/** Sets the specified value in the specified field, just like {@link #setField(int, IsoValue)}. */
public void update(int i, IsoValue<?> v) {
public <T> void update(int i, IsoValue<T> v) {
setField(i, v);
}
/** Returns the IsoValue in the specified field, just like {@link #getField(int)}. */
+12 -12
View File
@@ -178,27 +178,27 @@ public enum IsoType {
throw new IllegalArgumentException("Cannot format BigDecimal as " + this);
}
public IsoValue<Object> value(Object val, int len) {
return new IsoValue<Object>(this, val, len);
public <T> IsoValue<T> value(T val, int len) {
return new IsoValue<T>(this, val, len);
}
public IsoValue<Object> value(Object val) {
return new IsoValue<Object>(this, val);
public <T> IsoValue<T> value(T val) {
return new IsoValue<T>(this, val);
}
public IsoValue<Object> call(Object val, int len) {
return new IsoValue<Object>(this, val, len);
public <T> IsoValue<T> call(T val, int len) {
return new IsoValue<T>(this, val, len);
}
public IsoValue<Object> call(Object val) {
return new IsoValue<Object>(this, val);
public <T> IsoValue<T> call(T val) {
return new IsoValue<T>(this, val);
}
public IsoValue<Object> apply(Object val, int len) {
return new IsoValue<Object>(this, val, len);
public <T> IsoValue<T> apply(T val, int len) {
return new IsoValue<T>(this, val, len);
}
public IsoValue<Object> apply(Object val) {
return new IsoValue<Object>(this, val);
public <T> IsoValue<T> apply(T val) {
return new IsoValue<T>(this, val);
}
}
+20 -26
View File
@@ -23,6 +23,7 @@ import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.Date;
import com.solab.iso8583.util.Bcd;
import com.solab.iso8583.util.HexCodec;
/** Represents a value that is stored in a field inside an ISO8583 message.
@@ -82,8 +83,9 @@ public class IsoValue<T> implements Cloneable {
} else {
length = value.toString().length() / 2 + (value.toString().length() % 2);
}
} else if (custom instanceof CustomBinaryField) {
length = ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
} else {
//TODO special encoder, NO strings
String enc = custom.encodeField(value);
if (enc == null) {
enc = value == null ? "" : value.toString();
@@ -129,7 +131,13 @@ public class IsoValue<T> implements Cloneable {
}
} else if (t == IsoType.LLBIN || t == IsoType.LLLBIN) {
if (len == 0) {
//TODO customfield binary!
if (custom == null) {
length = ((byte[])val).length;
} else if (custom instanceof CustomBinaryField) {
length = ((CustomBinaryField<T>)custom).encodeBinaryField(value).length;
} else {
length = custom.encodeField(value).length();
}
length = custom == null ? ((byte[])val).length : custom.encodeField(value).length();
}
if (t == IsoType.LLBIN && length > 99) {
@@ -190,15 +198,17 @@ public class IsoValue<T> implements Cloneable {
return type.format((Date)value);
} else if (type == IsoType.BINARY) {
if (value instanceof byte[]) {
return type.format(encoder == null ? HexCodec.hexEncode((byte[])value) : encoder.encodeField(value), length * 2);
final byte[] _v = (byte[])value;
return type.format(encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value), length * 2);
} else {
return type.format(encoder == null ? value.toString() : encoder.encodeField(value), length * 2);
}
} else if (type == IsoType.LLBIN || type == IsoType.LLLBIN) {
if (value instanceof byte[]) {
return encoder == null ? HexCodec.hexEncode((byte[])value) : encoder.encodeField(value);
final byte[] _v = (byte[])value;
return encoder == null ? HexCodec.hexEncode(_v, 0, _v.length) : encoder.encodeField(value);
} else {
String _s = encoder == null ? value.toString() : encoder.encodeField(value);
final String _s = encoder == null ? value.toString() : encoder.encodeField(value);
return (_s.length() % 2 == 1) ? String.format("0%s", _s) : _s;
}
}
@@ -289,7 +299,7 @@ public class IsoValue<T> implements Cloneable {
}
//Encode in BCD if it's one of these types
if (buf != null) {
toBcd(toString(), buf);
Bcd.encode(toString(), buf);
outs.write(buf);
return;
}
@@ -299,6 +309,10 @@ public class IsoValue<T> implements Cloneable {
if (value instanceof byte[]) {
outs.write((byte[])value);
missing = length - ((byte[])value).length;
} else if (encoder instanceof CustomBinaryField) {
byte[] binval = ((CustomBinaryField<T>) encoder).encodeBinaryField(value);
outs.write(binval);
missing = length - binval.length;
} else {
byte[] binval = HexCodec.hexDecode(value.toString());
outs.write(binval);
@@ -314,24 +328,4 @@ public class IsoValue<T> implements Cloneable {
}
}
/** Encode the value as BCD and put it in the buffer. The buffer must be big enough
* to store the digits in the original value (half the length of the string). */
private void toBcd(String value, byte[] buf) {
int charpos = 0; //char where we start
int bufpos = 0;
if (value.length() % 2 == 1) {
//for odd lengths we encode just the first digit in the first byte
buf[0] = (byte)(value.charAt(0) - 48);
charpos = 1;
bufpos = 1;
}
//encode the rest of the string
while (charpos < value.length()) {
buf[bufpos] = (byte)(((value.charAt(charpos) - 48) << 4)
| (value.charAt(charpos + 1) - 48));
charpos += 2;
bufpos++;
}
}
}
@@ -56,9 +56,9 @@ public class MessageFactory<T extends IsoMessage> {
/** This map stores the message template for each message type. */
private Map<Integer, T> typeTemplates = new HashMap<Integer, T>();
/** Stores the information needed to parse messages sorted by type. */
private Map<Integer, Map<Integer, FieldParseInfo>> parseMap = new HashMap<Integer, Map<Integer, FieldParseInfo>>();
protected Map<Integer, Map<Integer, FieldParseInfo>> parseMap = new HashMap<Integer, Map<Integer, FieldParseInfo>>();
/** Stores the field numbers to be parsed, in order of appearance. */
private Map<Integer, List<Integer>> parseOrder = new HashMap<Integer, List<Integer>>();
protected Map<Integer, List<Integer>> parseOrder = new HashMap<Integer, List<Integer>>();
private TraceNumberGenerator traceGen;
/** The ISO header to be included in each message type. */
@@ -253,13 +253,13 @@ public class MessageFactory<T extends IsoMessage> {
* and the rest of the message must come. */
public T parseMessage(byte[] buf, int isoHeaderLength)
throws ParseException, UnsupportedEncodingException {
final int minlength = isoHeaderLength+(useBinary||binBitmap ? 10 : 20);
final int minlength = isoHeaderLength+(useBinary?2:4)+(binBitmap||useBinary ? 8:16);
if (buf.length < minlength) {
throw new ParseException("Insufficient buffer length, needs to be at least " + minlength, 0);
}
final T m = createIsoMessage(isoHeaderLength > 0 ? new String(buf, 0, isoHeaderLength) : null);
m.setCharacterEncoding(encoding);
int type = 0;
final int type;
if (useBinary) {
type = ((buf[isoHeaderLength] & 0xff) << 8) | (buf[isoHeaderLength + 1] & 0xff);
} else {
@@ -273,7 +273,8 @@ public class MessageFactory<T extends IsoMessage> {
final BitSet bs = new BitSet(64);
int pos = 0;
if (useBinary || binBitmap) {
for (int i = isoHeaderLength + 2; i < isoHeaderLength + 10; i++) {
final int bitmapStart = isoHeaderLength + (useBinary ? 2 : 4);
for (int i = bitmapStart; i < 8+bitmapStart; i++) {
int bit = 128;
for (int b = 0; b < 8; b++) {
bs.set(pos++, (buf[i] & bit) != 0);
@@ -285,7 +286,7 @@ public class MessageFactory<T extends IsoMessage> {
if (buf.length < minlength + 8) {
throw new ParseException("Insufficient length for secondary bitmap", minlength);
}
for (int i = isoHeaderLength + 10; i < isoHeaderLength + 18; i++) {
for (int i = 8+bitmapStart; i < 16+bitmapStart; i++) {
int bit = 128;
for (int b = 0; b < 8; b++) {
bs.set(pos++, (buf[i] & bit) != 0);
@@ -378,7 +379,7 @@ public class MessageFactory<T extends IsoMessage> {
log.warn("Field {} is not really in the message even though it's in the bitmap", i);
bs.clear(i - 1);
} else {
IsoValue<?> val = fpi.parseBinary(buf, pos, getCustomField(i));
IsoValue<?> val = fpi.parseBinary(i, buf, pos, getCustomField(i));
m.setField(i, val);
if (val != null) {
if (val.getType() == IsoType.NUMERIC || val.getType() == IsoType.DATE10
@@ -405,7 +406,7 @@ public class MessageFactory<T extends IsoMessage> {
log.warn("Field {} is not really in the message even though it's in the bitmap", i);
bs.clear(i - 1);
} else {
IsoValue<?> val = fpi.parse(buf, pos, getCustomField(i));
IsoValue<?> val = fpi.parse(i, buf, pos, getCustomField(i));
m.setField(i, val);
//To get the correct next position, we need to get the number of bytes, not chars
pos += val.toString().getBytes(fpi.getCharacterEncoding()).length;
@@ -419,6 +420,7 @@ public class MessageFactory<T extends IsoMessage> {
}
}
m.setBinary(useBinary);
m.setBinaryBitmap(binBitmap);
return m;
}
@@ -427,8 +429,9 @@ public class MessageFactory<T extends IsoMessage> {
* @param header
* @return IsoMessage
*/
@SuppressWarnings("unchecked")
protected T createIsoMessage(String header) {
return (T) new IsoMessage(header);
return (T)new IsoMessage(header);
}
/** Sets whether the factory should set the current date on newly created messages,
@@ -0,0 +1,40 @@
package com.solab.iso8583.codecs;
import com.solab.iso8583.CustomBinaryField;
import com.solab.iso8583.util.Bcd;
import java.math.BigInteger;
/**
* A custom field encoder/decoder to be used with LLBIN/LLLBIN fields
* that contain BigIntegers in BCD encoding.
*
* @author Enrique Zamudio
* Date: 07/05/13 13:02
*/
public class BigIntBcdCodec implements CustomBinaryField<BigInteger> {
@Override
public BigInteger decodeBinaryField(byte[] value, int pos, int len) {
return Bcd.decodeToBigInteger(value, pos, len*2);
}
@Override
public byte[] encodeBinaryField(BigInteger value) {
final String s = value.toString(10);
final byte[] buf = new byte[s.length() / 2 + s.length() % 2];
Bcd.encode(s, buf);
return buf;
}
@Override
public BigInteger decodeField(String value) {
return new BigInteger(value, 10);
}
@Override
public String encodeField(BigInteger value) {
return value.toString(10);
}
}
@@ -0,0 +1,37 @@
package com.solab.iso8583.codecs;
import com.solab.iso8583.CustomBinaryField;
import com.solab.iso8583.util.Bcd;
/**
* A custom field encoder/decoder to be used with LLBIN/LLLBIN fields
* that contain Longs in BCD encoding.
*
* @author Enrique Zamudio
* Date: 07/05/13 13:02
*/
public class LongBcdCodec implements CustomBinaryField<Long> {
@Override
public Long decodeBinaryField(byte[] value, int pos, int length) {
return Bcd.decodeToLong(value, pos, length*2);
}
@Override
public byte[] encodeBinaryField(Long value) {
final String s = Long.toString(value, 10);
final byte[] buf = new byte[s.length() / 2 + s.length() % 2];
Bcd.encode(s, buf);
return buf;
}
@Override
public Long decodeField(String value) {
return Long.parseLong(value, 10);
}
@Override
public String encodeField(Long value) {
return value.toString();
}
}
@@ -35,27 +35,34 @@ public abstract class AlphaNumericFieldParseInfo extends FieldParseInfo {
super(t, len);
}
public IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom) throws ParseException, UnsupportedEncodingException {
@Override
public <T> IsoValue<?> parse(final int field, final byte[] buf, final int pos,
final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid ALPHA/NUM position %d", pos), pos);
throw new ParseException(String.format("Invalid ALPHA/NUM field %d position %d",
field, pos), pos);
} else if (pos+length > buf.length) {
throw new ParseException(String.format("Insufficient data for %s field of length %d, pos %d",
type, length, pos), pos);
}
String _v = new String(buf, pos, length, getCharacterEncoding());
if (_v.length() != length) {
_v = new String(buf, pos, buf.length-pos, getCharacterEncoding()).substring(0, length);
}
if (custom == null) {
return new IsoValue<String>(type, _v, length, null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(_v), length, custom);
if (v.getValue() == null) {
return new IsoValue<String>(type, _v, length, null);
}
return v;
throw new ParseException(String.format("Insufficient data for %s field %d of length %d, pos %d",
type, field, length, pos), pos);
}
try {
String _v = new String(buf, pos, length, getCharacterEncoding());
if (_v.length() != length) {
_v = new String(buf, pos, buf.length-pos, getCharacterEncoding()).substring(0, length);
}
if (custom == null) {
return new IsoValue<String>(type, _v, length, null);
} else {
T decoded = custom.decodeField(_v);
return decoded == null ? new IsoValue<String>(type, _v, length, null) :
new IsoValue<T>(type, decoded, length, custom);
}
} catch (StringIndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for %s field %d of length %d, pos %d",
type, field, length, pos), pos);
}
}
}
@@ -35,24 +35,32 @@ public class AlphaParseInfo extends AlphaNumericFieldParseInfo {
super(IsoType.ALPHA, len);
}
public IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom)
throws ParseException, UnsupportedEncodingException {
@Override
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf, final int pos,
final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin ALPHA position %d", pos), pos);
throw new ParseException(String.format("Invalid bin ALPHA field %d position %d",
field, pos), pos);
} else if (pos+length > buf.length) {
throw new ParseException(String.format("Insufficient data for bin %s field of length %d, pos %d",
type, length, pos), pos);
}
if (custom == null) {
return new IsoValue<String>(type, new String(buf, pos, length, getCharacterEncoding()), length, null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(new String(buf, pos, length, getCharacterEncoding())), length, custom);
if (v.getValue() == null) {
return new IsoValue<String>(type, new String(buf, pos, length, getCharacterEncoding()), length, null);
}
return v;
throw new ParseException(String.format(
"Insufficient data for bin %s field %d of length %d, pos %d",
type, field, length, pos), pos);
}
try {
if (custom == null) {
return new IsoValue<String>(type, new String(buf, pos, length, getCharacterEncoding()), length, null);
} else {
T decoded = custom.decodeField(new String(buf, pos, length, getCharacterEncoding()));
return decoded == null ?
new IsoValue<String>(type, new String(buf, pos, length, getCharacterEncoding()), length, null) :
new IsoValue<T>(type, decoded, length, custom);
}
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for bin %s field %d of length %d, pos %d",
type, field, length, pos), pos);
}
}
}
@@ -37,23 +37,34 @@ public class AmountParseInfo extends FieldParseInfo {
super(IsoType.AMOUNT, 12);
}
public IsoValue<BigDecimal> parse(byte[] buf, int pos, CustomField<?> custom)
@Override
public <T> IsoValue<BigDecimal> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid AMOUNT position %d", pos), pos);
throw new ParseException(String.format("Invalid AMOUNT field %d position %d",
field, pos), pos);
}
if (pos+12 > buf.length) {
throw new ParseException(String.format("Insufficient data for AMOUNT field, pos %d", pos), pos);
throw new ParseException(String.format("Insufficient data for AMOUNT field %d, pos %d",
field, pos), pos);
}
String c = new String(buf, pos, 12, getCharacterEncoding());
try {
return new IsoValue<BigDecimal>(type, new BigDecimal(c).movePointLeft(2), null);
return new IsoValue<BigDecimal>(type, new BigDecimal(c).movePointLeft(2));
} catch (NumberFormatException ex) {
throw new ParseException(String.format("Cannot read amount '%s' pos %d", c, pos), pos);
throw new ParseException(String.format("Cannot read amount '%s' field %d pos %d",
c, field, pos), pos);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for AMOUNT field %d, pos %d", field, pos), pos);
}
}
public IsoValue<BigDecimal> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
@Override
public <T> IsoValue<BigDecimal> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
char[] digits = new char[13];
digits[10] = '.';
int start = 0;
@@ -65,9 +76,13 @@ public class AmountParseInfo extends FieldParseInfo {
}
}
try {
return new IsoValue<BigDecimal>(IsoType.AMOUNT, new BigDecimal(new String(digits)), null);
return new IsoValue<BigDecimal>(IsoType.AMOUNT, new BigDecimal(new String(digits)));
} catch (NumberFormatException ex) {
throw new ParseException(String.format("Cannot read amount '%s' pos %d", new String(digits), pos), pos);
throw new ParseException(String.format("Cannot read amount '%s' field %d pos %d",
new String(digits), field, pos), pos);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for AMOUNT field %d, pos %d", field, pos), pos);
}
}
@@ -38,41 +38,48 @@ public class BinaryParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom)
public <T> IsoValue<?> parse(final int field, final byte[] buf, final int pos,
final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid BINARY position %d", pos), pos);
throw new ParseException(String.format("Invalid BINARY field %d position %d",
field, pos), pos);
}
if (pos+(length*2) > buf.length) {
throw new ParseException(String.format("Insufficient data for BINARY field of length %d, pos %d",
length, pos), pos);
throw new ParseException(String.format(
"Insufficient data for BINARY field %d of length %d, pos %d",
field, length, pos), pos);
}
byte[] binval = HexCodec.hexDecode(new String(buf, pos, length*2));
if (custom == null) {
return new IsoValue<byte[]>(type, binval, binval.length, null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(new String(buf, pos, length*2, getCharacterEncoding())), length, custom);
if (v.getValue() == null) {
return new IsoValue<byte[]>(type, binval, binval.length, null);
}
return v;
T dec = custom.decodeField(new String(buf, pos, length*2, getCharacterEncoding()));
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, length, custom);
}
}
@Override
public IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf, final int pos,
final CustomField<T> custom) throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid BINARY field %d position %d",
field, pos), pos);
}
if (pos+length > buf.length) {
throw new ParseException(String.format(
"Insufficient data for BINARY field %d of length %d, pos %d",
field, length, pos), pos);
}
byte[] _v = new byte[length];
System.arraycopy(buf, pos, _v, 0, length);
if (custom == null) {
return new IsoValue<byte[]>(type, _v, length, null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(HexCodec.hexEncode(_v)), length, custom);
if (v.getValue() == null) {
return new IsoValue<byte[]>(type, _v, length, null);
}
return v;
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
return dec == null ? new IsoValue<byte[]>(type, _v, length, null) :
new IsoValue<T>(type, dec, length, custom);
}
}
@@ -50,21 +50,37 @@ public class ConfigParser {
private final static Logger log = LoggerFactory.getLogger(ConfigParser.class);
/** Creates a message factory configured from the default file, which is j8583.xml
* located in the root of the classpath, using the specified ClassLoader. */
public static MessageFactory<IsoMessage> createDefault(
ClassLoader loader) throws IOException {
if (loader.getResource("j8583.xml") == null) {
log.warn("ISO8583 ConfigParser cannot find j8583.xml, returning empty message factory");
return new MessageFactory<IsoMessage>();
} else {
return createFromClasspathConfig(loader, "j8583.xml");
}
}
/** Creates a message factory configured from the default file, which is j8583.xml
* located in the root of the classpath. */
public static MessageFactory createDefault() throws IOException {
if (MessageFactory.class.getClassLoader().getResource("j8583.xml") == null) {
log.warn("ISO8583 ConfigParser cannot find j8583.xml, returning empty message factory");
return new MessageFactory();
} else {
return createFromClasspathConfig("j8583.xml");
}
* located in the root of the classpath, using the MessageFactory's
* ClassLoader. */
public static MessageFactory<IsoMessage> createDefault() throws IOException {
return createDefault(MessageFactory.class.getClassLoader());
}
/** Creates a message factory from the specified path inside the classpath. */
public static MessageFactory createFromClasspathConfig(String path) throws IOException {
InputStream ins = MessageFactory.class.getClassLoader().getResourceAsStream(path);
MessageFactory mfact = new MessageFactory();
/** Creates a message factory from the specified path inside the classpath,
* using the specified ClassLoader. */
public static MessageFactory<IsoMessage> createFromClasspathConfig(
String path) throws IOException {
return createFromClasspathConfig(MessageFactory.class.getClassLoader(), path);
}
/** Creates a message factory from the specified path inside the classpath,
* using MessageFactory's ClassLoader. */
public static MessageFactory<IsoMessage> createFromClasspathConfig(
ClassLoader loader, String path) throws IOException {
InputStream ins = loader.getResourceAsStream(path);
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
if (ins != null) {
log.debug("ISO8583 Parsing config from classpath file {}", path);
try {
@@ -79,8 +95,8 @@ public class ConfigParser {
}
/** Creates a message factory from the file located at the specified URL. */
public static MessageFactory createFromUrl(URL url) throws IOException {
MessageFactory mfact = new MessageFactory();
public static MessageFactory<IsoMessage> createFromUrl(URL url) throws IOException {
MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
InputStream stream = url.openStream();
try {
parse(mfact, stream);
@@ -93,7 +109,8 @@ public class ConfigParser {
/** Reads the XML from the stream and configures the message factory with its values.
* @param mfact The message factory to be configured with the values read from the XML.
* @param stream The InputStream containing the XML configuration. */
protected static void parse(MessageFactory mfact, InputStream stream) throws IOException {
protected static <T extends IsoMessage> void parse(
MessageFactory<T> mfact, InputStream stream) throws IOException {
final DocumentBuilderFactory docfact = DocumentBuilderFactory.newInstance();
DocumentBuilder docb = null;
Document doc = null;
@@ -152,7 +169,8 @@ public class ConfigParser {
throw new IOException("Invalid ISO8583 type for template: " + elem.getAttribute("type"));
}
NodeList fields = elem.getElementsByTagName("field");
IsoMessage m = new IsoMessage();
@SuppressWarnings("unchecked")
T m = (T)new IsoMessage();
m.setType(type);
m.setCharacterEncoding(mfact.getCharacterEncoding());
for (int j = 0; j < fields.getLength(); j++) {
@@ -197,8 +215,9 @@ public class ConfigParser {
/** Configures a MessageFactory using the default configuration file j8583.xml. This is useful
* if you have a MessageFactory created using Spring for example. */
public static void configureFromDefault(MessageFactory mfact) throws IOException {
if (MessageFactory.class.getClassLoader().getResource("j8583.xml") == null) {
public static <T extends IsoMessage> void configureFromDefault(
MessageFactory<T> mfact) throws IOException {
if (mfact.getClass().getClassLoader().getResource("j8583.xml") == null) {
log.warn("ISO8583 config file j8583.xml not found!");
} else {
configureFromClasspathConfig(mfact, "j8583.xml");
@@ -207,7 +226,8 @@ public class ConfigParser {
/** This method attempts to open a stream from the XML configuration in the specified URL and
* configure the message factory from that config. */
public static void configureFromUrl(MessageFactory mfact, URL url) throws IOException {
public static <T extends IsoMessage> void configureFromUrl(
MessageFactory<T> mfact, URL url) throws IOException {
InputStream stream = url.openStream();
try {
parse(mfact, stream);
@@ -219,8 +239,9 @@ public class ConfigParser {
/** Configures a MessageFactory using the configuration file at the path specified (will be searched
* within the classpath using the MessageFactory's ClassLoader). This is useful for configuring
* Spring-bound instances of MessageFactory for example. */
public static void configureFromClasspathConfig(MessageFactory mfact, String path) throws IOException {
InputStream ins = MessageFactory.class.getClassLoader().getResourceAsStream(path);
public static <T extends IsoMessage> void configureFromClasspathConfig(
MessageFactory<T> mfact, String path) throws IOException {
InputStream ins = mfact.getClass().getClassLoader().getResourceAsStream(path);
if (ins != null) {
log.debug("ISO8583 Parsing config from classpath file {}", path);
try {
@@ -42,13 +42,16 @@ public class Date10ParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parse(byte[] buf, int pos, CustomField<?> custom)
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE10 position %d", pos), pos);
throw new ParseException(String.format("Invalid DATE10 field %d position %d",
field, pos), pos);
}
if (pos+10 > buf.length) {
throw new ParseException(String.format("Insufficient data for DATE10 field, pos %d", pos), pos);
throw new ParseException(String.format("Insufficient data for DATE10 field %d, pos %d",
field, pos), pos);
}
//A SimpleDateFormat in the case of dates won't help because of the missing data
//we have to use the current date for reference and change what comes in the buffer
@@ -65,7 +68,17 @@ public class Date10ParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE10 field %d position %d",
field, pos), pos);
}
if (pos+5 > buf.length) {
throw new ParseException(String.format("Insufficient data for DATE10 field %d, pos %d",
field, pos), pos);
}
int[] tens = new int[5];
int start = 0;
for (int i = pos; i < pos + tens.length; i++) {
@@ -37,12 +37,15 @@ public class Date4ParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parse(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parse(final int field, final byte[] buf, final int pos,
final CustomField<T> custom) throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE4 position %d", pos), pos);
throw new ParseException(String.format("Invalid DATE4 field %d position %d",
field, pos), pos);
}
if (pos+4 > buf.length) {
throw new ParseException(String.format("Insufficient data for DATE4 field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for DATE4 field %d, pos %d", field, pos), pos);
}
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR, 0);
@@ -57,9 +60,15 @@ public class Date4ParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf, final int pos,
final CustomField<T> custom) throws ParseException {
int[] tens = new int[2];
int start = 0;
if (buf.length-pos < 2) {
throw new ParseException(String.format(
"Insufficient data to parse binary DATE4 field %d pos %d",
field, pos), pos);
}
for (int i = pos; i < pos + tens.length; i++) {
tens[start++] = (((buf[i] & 0xf0) >> 4) * 10) + (buf[i] & 0x0f);
}
@@ -37,12 +37,16 @@ public class DateExpParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parse(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE_EXP position %d", pos), pos);
throw new ParseException(String.format("Invalid DATE_EXP field %d position %d",
field, pos), pos);
}
if (pos+4 > buf.length) {
throw new ParseException(String.format("Insufficient data for DATE_EXP field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for DATE_EXP field %d pos %d", field, pos), pos);
}
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR, 0);
@@ -57,7 +61,17 @@ public class DateExpParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid DATE_EXP field %d position %d",
field, pos), pos);
}
if (pos+2 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for DATE_EXP field %d pos %d", field, pos), pos);
}
int[] tens = new int[2];
int start = 0;
for (int i = pos; i < pos + tens.length; i++) {
@@ -65,14 +65,24 @@ public abstract class FieldParseInfo {
}
/** Parses the character data from the buffer and returns the
* IsoValue with the correct data type in it. */
public abstract IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom)
throws ParseException, UnsupportedEncodingException;
* IsoValue with the correct data type in it.
* @param field The field index, useful for error reporting.
* @param buf The full ISO message buffer.
* @param pos The starting position for the field data.
* @param custom A CustomField to decode the field. */
public abstract <T> IsoValue<?> parse(final int field, byte[] buf, int pos,
CustomField<T> custom)
throws ParseException, UnsupportedEncodingException;
/** Parses binary data from the buffer, creating and returning an IsoValue of the configured
* type and length. */
public abstract IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom)
throws ParseException, UnsupportedEncodingException;
* type and length.
* @param field The field index, useful for error reporting.
* @param buf The full ISO message buffer.
* @param pos The starting position for the field data.
* @param custom A CustomField to decode the field. */
public abstract <T> IsoValue<?> parseBinary(final int field, byte[] buf, int pos,
CustomField<T> custom)
throws ParseException, UnsupportedEncodingException;
/** Returns a new FieldParseInfo instance that can parse the specified type. */
public static FieldParseInfo getInstance(IsoType t, int len, String encoding) {
@@ -20,6 +20,7 @@ package com.solab.iso8583.parse;
import java.text.ParseException;
import com.solab.iso8583.CustomBinaryField;
import com.solab.iso8583.CustomField;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
@@ -37,58 +38,90 @@ public class LlbinParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<?> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLBIN position %d", pos), pos);
throw new ParseException(String.format("Invalid LLBIN field %d position %d",
field, pos), pos);
} else if (pos+2 > buf.length) {
throw new ParseException("Insufficient LLBIN header", pos);
throw new ParseException(String.format("Insufficient LLBIN header field %d",
field), pos);
}
length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48);
if (length < 0) {
throw new ParseException(String.format("Invalid LLBIN length %d pos %d", length, pos), pos);
throw new ParseException(String.format("Invalid LLBIN field %d length %d pos %d",
field, length, pos), pos);
}
if (length+pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLBIN field, pos %d (LEN states '%s')", pos, new String(buf, pos, 2)), pos);
throw new ParseException(String.format(
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
field, pos, new String(buf, pos, 2)), pos);
}
byte[] binval = length == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 2, length));
if (custom == null) {
return new IsoValue<byte[]>(type, binval, binval.length, null);
} else if (custom instanceof CustomBinaryField) {
try {
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 2, length);
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, 0, custom);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
field, pos, new String(buf, pos, 2)), pos);
}
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(
new String(buf, pos + 2, length)), binval.length, custom);
if (v.getValue() == null) {
return new IsoValue<byte[]>(type, binval, binval.length, null);
}
return v;
try {
T dec = custom.decodeField(new String(buf, pos + 2, length));
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, binval.length, custom);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLBIN field %d, pos %d (LEN states '%s')",
field, pos, new String(buf, pos, 2)), pos);
}
}
}
@Override
public IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin LLBIN position %d", pos), pos);
throw new ParseException(String.format("Invalid bin LLBIN field %d position %d",
field, pos), pos);
} else if (pos+1 > buf.length) {
throw new ParseException("Insufficient bin LLBIN header", pos);
throw new ParseException(String.format("Insufficient bin LLBIN header field %d",
field), pos);
}
length = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
if (length < 0) {
final int l = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
if (l < 0) {
throw new ParseException(String.format("Invalid bin LLBIN length %d pos %d", length, pos), pos);
}
if (length+pos+1 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin LLBIN field, pos %d", pos), pos);
if (l+pos+1 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for bin LLBIN field %d, pos %d: need %d, only %d available",
field, pos, l, buf.length), pos);
}
byte[] _v = new byte[length];
System.arraycopy(buf, pos+1, _v, 0, length);
byte[] _v = new byte[l];
System.arraycopy(buf, pos+1, _v, 0, l);
if (custom == null) {
return new IsoValue<byte[]>(type, _v, null);
} else if (custom instanceof CustomBinaryField) {
try {
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(buf, pos + 1, l);
return dec == null ? new IsoValue<byte[]>(type, _v, _v.length, null) :
new IsoValue<T>(type, dec, l, custom);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLBIN field %d, pos %d length %d",
field, pos, l), pos);
}
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(HexCodec.hexEncode(_v)), custom);
if (v.getValue() == null) {
return new IsoValue<byte[]>(type, _v, null);
}
return v;
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
return dec == null ? new IsoValue<byte[]>(type, _v, null) :
new IsoValue<T>(type, dec, custom);
}
}
@@ -20,6 +20,7 @@ package com.solab.iso8583.parse;
import java.text.ParseException;
import com.solab.iso8583.CustomBinaryField;
import com.solab.iso8583.CustomField;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
@@ -37,61 +38,93 @@ public class LllbinParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<?> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLLBIN position %d", pos), pos);
throw new ParseException(String.format("Invalid LLLBIN field %d pos %d",
field, pos), pos);
} else if (pos+3 > buf.length) {
throw new ParseException("Insufficient LLLBIN header", pos);
throw new ParseException(String.format("Insufficient LLLBIN header field %d",
field), pos);
}
if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) {
throw new ParseException(String.format("Invalid LLLBIN length '%s' pos %d", new String(buf, pos, 3), pos), pos);
throw new ParseException(String.format("Invalid LLLBIN length '%s' field %d pos %d",
new String(buf, pos, 3), field, pos), pos);
}
length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
if (length < 0) {
throw new ParseException(String.format("Invalid LLLBIN length %d pos %d", length, pos), pos);
} else if (length+pos+3 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLLBIN field, pos %d", pos), pos);
final int l = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
if (l < 0) {
throw new ParseException(String.format("Invalid LLLBIN length %d field %d pos %d",
l, field, pos), pos);
} else if (l+pos+3 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
}
byte[] binval = length == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 3, length));
byte[] binval = l == 0 ? new byte[0] : HexCodec.hexDecode(new String(buf, pos + 3, l));
if (custom == null) {
return new IsoValue<byte[]>(type, binval, binval.length, null);
} else if (custom instanceof CustomBinaryField) {
try {
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(
buf, pos + 3, l);
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, 0, custom);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
}
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(
length == 0 ? "" : new String(buf, pos + 3, length)), length, custom);
if (v.getValue() == null) {
//problems decoding? return the string
return new IsoValue<byte[]>(type, binval, binval.length, null);
}
return v;
try {
T dec = custom.decodeField(
l == 0 ? "" : new String(buf, pos + 3, l));
return dec == null ? new IsoValue<byte[]>(type, binval, binval.length, null) :
new IsoValue<T>(type, dec, l, custom);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
}
}
}
@Override
public IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin LLLBIN position %d", pos), pos);
} else if (pos+3 > buf.length) {
throw new ParseException("Insufficient bin LLLBIN header", pos);
throw new ParseException(String.format("Invalid bin LLLBIN field %d pos %d",
field, pos), pos);
} else if (pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient LLLBIN header field %d",
field), pos);
}
length = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
if (length < 0) {
throw new ParseException(String.format("Invalid bin LLLBIN length %d pos %d", length, pos), pos);
final int l = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
if (l < 0) {
throw new ParseException(String.format("Invalid LLLBIN length %d field %d pos %d",
l, field, pos), pos);
}
if (length+pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin LLLBIN field, pos %d", pos), pos);
if (l+pos+2 > buf.length) {
throw new ParseException(String.format(
"Insufficient data for bin LLLBIN field %d, pos %d requires %d, only %d available",
field, pos, l, buf.length-pos+1), pos);
}
byte[] _v = new byte[length];
System.arraycopy(buf, pos+2, _v, 0, length);
byte[] _v = new byte[l];
System.arraycopy(buf, pos+2, _v, 0, l);
if (custom == null) {
return new IsoValue<byte[]>(type, _v, null);
} else if (custom instanceof CustomBinaryField) {
try {
T dec = ((CustomBinaryField<T>)custom).decodeBinaryField(
buf, pos + 2, l);
return dec == null ? new IsoValue<byte[]>(type, _v, _v.length, null) :
new IsoValue<T>(type, dec, l, custom);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLLBIN field %d, pos %d", field, pos), pos);
}
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(HexCodec.hexEncode(_v)), custom);
if (v.getValue() == null) {
return new IsoValue<byte[]>(type, _v, null);
}
return v;
T dec = custom.decodeField(HexCodec.hexEncode(_v, 0, _v.length));
return dec == null ? new IsoValue<byte[]>(type, _v, null) :
new IsoValue<T>(type, dec, custom);
}
}
@@ -36,24 +36,35 @@ public class LllvarParseInfo extends FieldParseInfo {
super(IsoType.LLLVAR, 0);
}
public IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom)
public <T> IsoValue<?> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLLVAR position %d", pos), pos);
throw new ParseException(String.format("Invalid LLLVAR field %d pos %d",
field, pos), pos);
} else if (pos+3 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLLVAR header, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for LLLVAR header field %d pos %d", field, pos), pos);
}
if (!(Character.isDigit(buf[pos]) && Character.isDigit(buf[pos+1]) && Character.isDigit(buf[pos+2]))) {
throw new ParseException(String.format("Invalid LLLVAR length '%s' pos %d",
new String(buf, pos, 3), pos), pos);
throw new ParseException(String.format("Invalid LLLVAR length '%s' field %d pos %d",
new String(buf, pos, 3), field, pos), pos);
}
length = ((buf[pos] - 48) * 100) + ((buf[pos + 1] - 48) * 10) + (buf[pos + 2] - 48);
if (length < 0) {
throw new ParseException(String.format("Invalid LLLVAR length %d pos %d", length, pos), pos);
throw new ParseException(String.format("Invalid LLLVAR length %d field %d pos %d",
length, field, pos), pos);
} else if (length+pos+3 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLLVAR field, pos %d", pos), pos);
throw new ParseException(String.format("Insufficient data for LLLVAR field %d, pos %d",
field, pos), pos);
}
String _v = length == 0 ? "" : new String(buf, pos + 3, length, getCharacterEncoding());
String _v;
try {
_v = length == 0 ? "" : new String(buf, pos + 3, length, getCharacterEncoding());
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLLVAR header, field %d pos %d", field, pos), pos);
}
//This is new: if the String's length is different from the specified length in the buffer,
//there are probably some extended characters. So we create a String from the rest of the buffer,
//and then cut it to the specified length.
@@ -63,30 +74,34 @@ public class LllvarParseInfo extends FieldParseInfo {
if (custom == null) {
return new IsoValue<String>(type, _v, length, null);
} else {
Object decoded = custom.decodeField(_v);
T decoded = custom.decodeField(_v);
//If decode fails, return string; otherwise use the decoded object and its codec
return new IsoValue(type, decoded == null ? _v : decoded, length, decoded == null ? null : custom);
return decoded == null ? new IsoValue<String>(type, _v, length, null) :
new IsoValue<T>(type, decoded, length, custom);
}
}
public IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom)
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin LLLVAR position %d", pos), pos);
throw new ParseException(String.format("Invalid bin LLLVAR field %d pos %d", field, pos), pos);
} else if (pos+3 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin LLLVAR header, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for bin LLLVAR header, field %d pos %d", field, pos), pos);
}
length = ((buf[pos] & 0x0f) * 100) + (((buf[pos + 1] & 0xf0) >> 4) * 10) + (buf[pos + 1] & 0x0f);
if (length < 0) {
throw new ParseException(String.format("Invalid bin LLLVAR length %d pos %d", length, pos), pos);
throw new ParseException(String.format(
"Invalid bin LLLVAR length %d, field %d pos %d", length, field, pos), pos);
} else if (length+pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin LLLVAR field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for bin LLLVAR field %d, pos %d", field, pos), pos);
}
if (custom == null) {
return new IsoValue<String>(type, new String(buf, pos + 2, length, getCharacterEncoding()), null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(new String(buf, pos + 2, length, getCharacterEncoding())), custom);
IsoValue<T> v = new IsoValue<T>(type, custom.decodeField(new String(buf, pos + 2, length, getCharacterEncoding())), custom);
if (v.getValue() == null) {
return new IsoValue<String>(type, new String(buf, pos + 2, length, getCharacterEncoding()), null);
}
@@ -35,20 +35,30 @@ public class LlvarParseInfo extends FieldParseInfo {
super(IsoType.LLVAR, 0);
}
public IsoValue<?> parse(byte[] buf, int pos, CustomField<?> custom)
@Override
public <T> IsoValue<?> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid LLVAR position %d", pos), pos);
throw new ParseException(String.format("Invalid LLVAR field %d %d", field, pos), pos);
} else if (pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLVAR header, pos %d", pos), pos);
}
length = ((buf[pos] - 48) * 10) + (buf[pos + 1] - 48);
if (length < 0) {
throw new ParseException(String.format("Invalid LLVAR length %d pos %d", length, pos), pos);
throw new ParseException(String.format(
"Invalid LLVAR length %d, field %d pos %d", length, field, pos), pos);
} else if (length+pos+2 > buf.length) {
throw new ParseException(String.format("Insufficient data for LLVAR field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for LLVAR field %d, pos %d", field, pos), pos);
}
String _v = length == 0 ? "" : new String(buf, pos + 2, length, getCharacterEncoding());
String _v;
try {
_v = length == 0 ? "" : new String(buf, pos + 2, length, getCharacterEncoding());
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for LLVAR header, field %d pos %d", field, pos), pos);
}
//This is new: if the String's length is different from the specified length in the buffer,
//there are probably some extended characters. So we create a String from the rest of the buffer,
//and then cut it to the specified length.
@@ -58,38 +68,38 @@ public class LlvarParseInfo extends FieldParseInfo {
if (custom == null) {
return new IsoValue<String>(type, _v, length, null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(_v), length, custom);
if (v.getValue() == null) {
return new IsoValue<String>(type, _v, length, null);
}
return v;
T dec = custom.decodeField(_v);
return dec == null ? new IsoValue<String>(type, _v, length, null) :
new IsoValue<T>(type, dec, length, custom);
}
}
public IsoValue<?> parseBinary(byte[] buf, int pos, CustomField<?> custom)
@Override
public <T> IsoValue<?> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException, UnsupportedEncodingException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin LLVAR position %d", pos), pos);
throw new ParseException(String.format("Invalid bin LLVAR field %d pos %d",
field, pos), pos);
} else if (pos+1 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin LLVAR header, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for bin LLVAR header, field %d pos %d", field, pos), pos);
}
length = (((buf[pos] & 0xf0) >> 4) * 10) + (buf[pos] & 0x0f);
if (length < 0) {
throw new ParseException(String.format("Invalid bin LLVAR length %d pos %d", length, pos), pos);
throw new ParseException(String.format(
"Invalid bin LLVAR length %d, field %d pos %d", length, field, pos), pos);
}
if (length+pos+1 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin LLVAR field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for bin LLVAR field %d, pos %d", field, pos), pos);
}
if (custom == null) {
return new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null);
} else {
@SuppressWarnings({"unchecked", "rawtypes"})
IsoValue<?> v = new IsoValue(type, custom.decodeField(new String(buf, pos + 1, length, getCharacterEncoding())), custom);
if (v.getValue() == null) {
return new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null);
}
return v;
T dec = custom.decodeField(new String(buf, pos + 1, length, getCharacterEncoding()));
return dec == null ? new IsoValue<String>(type, new String(buf, pos + 1, length, getCharacterEncoding()), null) :
new IsoValue<T>(type, dec, custom);
}
}
@@ -24,6 +24,7 @@ import java.text.ParseException;
import com.solab.iso8583.CustomField;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.util.Bcd;
/** This class is used to parse NUMERIC fields.
*
@@ -35,33 +36,32 @@ public class NumericParseInfo extends AlphaNumericFieldParseInfo {
super(IsoType.NUMERIC, len);
}
public IsoValue<Number> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
@Override
public <T> IsoValue<Number> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin NUMERIC position %d", pos), pos);
throw new ParseException(String.format("Invalid bin NUMERIC field %d pos %d",
field, pos), pos);
} else if (pos+(length/2) > buf.length) {
throw new ParseException(String.format("Insufficient data for bin %s field of length %d, pos %d",
type, length, pos), pos);
throw new ParseException(String.format(
"Insufficient data for bin %s field %d of length %d, pos %d",
type, field, length, pos), pos);
}
//A long covers up to 18 digits
if (length < 19) {
long l = 0;
long power = 1L;
for (int i = pos + (length / 2) + (length % 2) - 1; i >= pos; i--) {
l += (buf[i] & 0x0f) * power;
power *= 10L;
l += ((buf[i] & 0xf0) >> 4) * power;
power *= 10L;
}
return new IsoValue<Number>(IsoType.NUMERIC, l, length, null);
return new IsoValue<Number>(IsoType.NUMERIC, Bcd.decodeToLong(buf, pos, length),
length, null);
} else {
//Use a BigInteger
char[] digits = new char[length];
int start = 0;
for (int i = pos; i < pos + (length / 2) + (length % 2); i++) {
digits[start++] = (char)(((buf[i] & 0xf0) >> 4) + 48);
digits[start++] = (char)((buf[i] & 0x0f) + 48);
}
return new IsoValue<Number>(IsoType.NUMERIC, new BigInteger(new String(digits)), length, null);
try {
return new IsoValue<Number>(IsoType.NUMERIC,
Bcd.decodeToBigInteger(buf, pos, length), length, null);
} catch (IndexOutOfBoundsException ex) {
throw new ParseException(String.format(
"Insufficient data for bin %s field %d of length %d, pos %d",
type, field, length, pos), pos);
}
}
}
@@ -38,11 +38,15 @@ public class TimeParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parse(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parse(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid TIME position %d", pos), pos);
throw new ParseException(String.format("Invalid TIME field %d pos %d",
field, pos), pos);
} else if (pos+6 > buf.length) {
throw new ParseException(String.format("Insufficient data for TIME field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for TIME field %d, pos %d", field, pos), pos);
}
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, ((buf[pos] - 48) * 10) + buf[pos + 1] - 48);
@@ -52,11 +56,15 @@ public class TimeParseInfo extends FieldParseInfo {
}
@Override
public IsoValue<Date> parseBinary(byte[] buf, int pos, CustomField<?> custom) throws ParseException {
public <T> IsoValue<Date> parseBinary(final int field, final byte[] buf,
final int pos, final CustomField<T> custom)
throws ParseException {
if (pos < 0) {
throw new ParseException(String.format("Invalid bin TIME position %d", pos), pos);
throw new ParseException(String.format("Invalid bin TIME field %d pos %d",
field, pos), pos);
} else if (pos+3 > buf.length) {
throw new ParseException(String.format("Insufficient data for bin TIME field, pos %d", pos), pos);
throw new ParseException(String.format(
"Insufficient data for bin TIME field %d, pos %d", field, pos), pos);
}
int[] tens = new int[3];
int start = 0;
@@ -0,0 +1,71 @@
package com.solab.iso8583.util;
import java.math.BigInteger;
import java.text.ParseException;
/**
* Routines for Binary Coded Digits.
*
* @author Enrique Zamudio
* Date: 23/04/13 11:24
*/
public final class Bcd {
private Bcd(){}
/** Decodes a BCD-encoded number as a long.
* @param buf The byte buffer containing the BCD data.
* @param pos The starting position in the buffer.
* @param length The number of DIGITS (not bytes) to read. */
public static long decodeToLong(byte[] buf, int pos, int length)
throws IndexOutOfBoundsException {
if (length > 18) {
throw new IndexOutOfBoundsException("Buffer too big to decode as long");
}
long l = 0;
long power = 1L;
for (int i = pos + (length / 2) + (length % 2) - 1; i >= pos; i--) {
l += (buf[i] & 0x0f) * power;
power *= 10L;
l += ((buf[i] & 0xf0) >> 4) * power;
power *= 10L;
}
return l;
}
/** Encode the value as BCD and put it in the buffer. The buffer must be big enough
* to store the digits in the original value (half the length of the string). */
public static void encode(String value, byte[] buf) {
int charpos = 0; //char where we start
int bufpos = 0;
if (value.length() % 2 == 1) {
//for odd lengths we encode just the first digit in the first byte
buf[0] = (byte)(value.charAt(0) - 48);
charpos = 1;
bufpos = 1;
}
//encode the rest of the string
while (charpos < value.length()) {
buf[bufpos] = (byte)(((value.charAt(charpos) - 48) << 4)
| (value.charAt(charpos + 1) - 48));
charpos += 2;
bufpos++;
}
}
/** Decodes a BCD-encoded number as a BigInteger.
* @param buf The byte buffer containing the BCD data.
* @param pos The starting position in the buffer.
* @param length The number of DIGITS (not bytes) to read. */
public static BigInteger decodeToBigInteger(byte[] buf, int pos, int length)
throws IndexOutOfBoundsException {
char[] digits = new char[length];
int start = 0;
for (int i = pos; i < pos + (length / 2) + (length % 2); i++) {
digits[start++] = (char)(((buf[i] & 0xf0) >> 4) + 48);
digits[start++] = (char)((buf[i] & 0x0f) + 48);
}
return new BigInteger(new String(digits));
}
}
@@ -1,21 +1,24 @@
package com.solab.iso8583.util;
/** Utility class to perform HEX encoding/decoding of values. */
public class HexCodec {
public final class HexCodec {
static final char[] HEX = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static String hexEncode(byte[] buffer) {
private HexCodec(){}
public static String hexEncode(byte[] buffer, int start, int length) {
if (buffer.length == 0) {
return "";
}
int holder = 0;
char[] chars = new char[buffer.length * 2];
for (int i = 0; i < buffer.length; i++) {
char[] chars = new char[length * 2];
int pos = -1;
for (int i = start; i < start+length; i++) {
holder = (buffer[i] & 0xf0) >> 4;
chars[i * 2] = HEX[holder];
chars[++pos * 2] = HEX[holder];
holder = buffer[i] & 0x0f;
chars[(i * 2) + 1] = HEX[holder];
chars[(pos * 2) + 1] = HEX[holder];
}
return new String(chars);
}
@@ -31,7 +31,7 @@ public class SimpleParser {
}
public static void main(String [] args) throws IOException, ParseException {
final MessageFactory mf = new MessageFactory();
final MessageFactory<IsoMessage> mf = new MessageFactory<IsoMessage>();
if (args.length == 0) {
ConfigParser.configureFromDefault(mf);
} else {
+1 -1
View File
@@ -15,7 +15,7 @@
<item name="Download" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
</links>
<menu name="j8583 1.5.6">
<menu name="j8583 1.5.7">
<item name="Introduction" href="index.html"/>
<item name="API docs" href="javadoc/index.html" />
<item name="Beginner's Guide" href="guide.html"/>
+1 -1
View File
@@ -15,7 +15,7 @@
<item name="Descargar" href="http://sourceforge.net/projects/j8583/files/j8583/"/>
</links>
<menu name="j8583 1.5.6">
<menu name="j8583 1.5.7">
<item name="Introducción" href="index.html"/>
<item name="Documentación API" href="../javadoc/index.html" />
<item name="Guía de inicio" href="guide.html"/>
+104
View File
@@ -0,0 +1,104 @@
package j8583;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.codecs.BigIntBcdCodec;
import com.solab.iso8583.codecs.LongBcdCodec;
import com.solab.iso8583.parse.FieldParseInfo;
import com.solab.iso8583.parse.LlbinParseInfo;
import com.solab.iso8583.parse.LllbinParseInfo;
import com.solab.iso8583.util.HexCodec;
import org.junit.Assert;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.text.ParseException;
import java.util.HashMap;
/**
* Unit tests for the numeric CustomBinaryField codecs.
*
* @author Enrique Zamudio
* Date: 07/05/13 13:20
*/
public class CustomBinCodecs {
private final BigInteger b29 = new BigInteger("12345678901234567890123456789");
final byte[] longData2 = new byte[]{ 0x12, 0x34, 0x56, 0x78, (byte)0x90, 00, 00, 00, 00, 00 };
final byte[] bigintData1 = new byte[]{ 1, 0x23, 0x45, 0x67, (byte)0x89, 1, 0x23, 0x45, 0x67, (byte)0x89, 1, 0x23, 0x45, 0x67, (byte)0x89, 00, 00, 00, 00, 00 };
@Test
public void testLongCodec() {
final LongBcdCodec longCodec = new LongBcdCodec();
final byte[] data1 = new byte[]{ 1, 0x23, 0x45, (byte)0x67, (byte)0x89, 00, 00, 00, 00, 00 };
Assert.assertEquals(123456789l, (long) longCodec.decodeBinaryField(data1, 0, 5));
Assert.assertEquals(1234567890l, (long)longCodec.decodeBinaryField(longData2, 0, 5));
final byte[] cod1 = longCodec.encodeBinaryField(123456789l);
final byte[] cod2 = longCodec.encodeBinaryField(1234567890l);
for (int i = 0; i < 5; i++) {
Assert.assertEquals("LONG Data1 differs at pos " + i, data1[i], cod1[i]);
Assert.assertEquals("LONG Data2 differs at pos " + i, longData2[i], cod2[i]);
}
}
@Test
public void testBigIntCodec() {
final BigInteger b30 = new BigInteger("123456789012345678901234567890");
final BigIntBcdCodec bigintCodec = new BigIntBcdCodec();
final byte[] data2 = new byte[]{ 0x12, 0x34, 0x56, 0x78, (byte)0x90, 0x12, 0x34, 0x56, 0x78, (byte)0x90, 0x12, 0x34, 0x56, (byte)0x78, (byte)0x90, 00, 00, 00, 00, 00 };
Assert.assertEquals(b29, bigintCodec.decodeBinaryField(bigintData1, 0, 15));
Assert.assertEquals(b30, bigintCodec.decodeBinaryField(data2, 0, 15));
final byte[] cod1 = bigintCodec.encodeBinaryField(b29);
final byte[] cod2 = bigintCodec.encodeBinaryField(b30);
for (int i = 0; i < 15; i++) {
Assert.assertEquals("BIGINT Data1 differs at pos " + i, bigintData1[i], cod1[i]);
Assert.assertEquals("BIGINT Data2 differs at pos " + i, data2[i], cod2[i]);
}
}
private void testFieldType(final IsoType type, final FieldParseInfo fieldParser, int offset1, int offset2) throws UnsupportedEncodingException, ParseException {
final BigIntBcdCodec bigintCodec = new BigIntBcdCodec();
final LongBcdCodec longCodec = new LongBcdCodec();
final MessageFactory<IsoMessage> mfact = new MessageFactory<IsoMessage>();
IsoMessage tmpl = new IsoMessage();
tmpl.setBinary(true);
tmpl.setType(0x200);
tmpl.setValue(2, 1234567890l, longCodec, type, 0);
tmpl.setValue(3, b29, bigintCodec, type, 0);
mfact.addMessageTemplate(tmpl);
mfact.setCustomField(2, longCodec);
mfact.setCustomField(3, bigintCodec);
HashMap<Integer, FieldParseInfo> parser = new HashMap<Integer,FieldParseInfo>();
parser.put(2, fieldParser);
parser.put(3, fieldParser);
mfact.setParseMap(0x200, parser);
mfact.setUseBinaryMessages(true);
//Test encoding
tmpl = mfact.newMessage(0x200);
byte[] buf = tmpl.writeData();
System.out.println("MENSAJE: " + HexCodec.hexEncode(buf, 0, buf.length));
for (int i = 0; i < 5; i++) {
Assert.assertEquals("LONG Data differs at pos " + i, longData2[i], buf[i+offset1]);
}
for (int i = 0; i < 15; i++) {
Assert.assertEquals("BIGINT Data differs at pos " + i, bigintData1[i], buf[i+offset2]);
}
//Test parsing
tmpl = mfact.parseMessage(buf, 0);
Assert.assertEquals(new Long(1234567890l), tmpl.getObjectValue(2));
Assert.assertEquals(b29, tmpl.getObjectValue(3));
}
@Test
public void testLLBIN() throws ParseException, UnsupportedEncodingException {
testFieldType(IsoType.LLBIN, new LlbinParseInfo(), 11, 17);
}
@Test
public void testLLLBIN() throws ParseException, UnsupportedEncodingException {
testFieldType(IsoType.LLLBIN, new LllbinParseInfo(), 12, 19);
}
}
+25 -25
View File
@@ -4,10 +4,8 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Arrays;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -18,8 +16,8 @@ import com.solab.iso8583.util.HexCodec;
/** Test binary message encoding and binary fields. */
public class TestBinaries {
private MessageFactory mfactAscii = new MessageFactory();
private MessageFactory mfactBin = new MessageFactory();
private MessageFactory<IsoMessage> mfactAscii = new MessageFactory<IsoMessage>();
private MessageFactory<IsoMessage> mfactBin = new MessageFactory<IsoMessage>();
@Before
public void setup() throws IOException {
@@ -37,30 +35,28 @@ public class TestBinaries {
Assert.assertEquals(new BigDecimal("1234.00"), m.getObjectValue(4));
Assert.assertTrue("No field 7!", m.hasField(7));
Assert.assertEquals("Wrong trace", "000123", m.getField(11).toString());
byte[] buf = (byte[])m.getObjectValue(41);
byte[] buf = m.getObjectValue(41);
byte[] exp = new byte[]{ (byte)0xab, (byte)0xcd, (byte)0xef, 0, 0, 0, 0, 0};
Assert.assertEquals("Field 41 wrong length", 8, buf.length);
Assert.assertTrue("Field 41 wrong value", Arrays.equals(exp, buf));
buf = (byte[])m.getObjectValue(42);
Assert.assertArrayEquals("Field 41 wrong value", exp, buf);
buf = m.getObjectValue(42);
exp = new byte[]{ (byte)0x0a, (byte)0xbc, (byte)0xde, 0 };
Assert.assertEquals("field 42 wrong length", 4, buf.length);
Assert.assertTrue("Field 42 wrong value", Arrays.equals(exp, buf));
Assert.assertArrayEquals("Field 42 wrong value", exp, buf);
Assert.assertTrue(((String)m.getObjectValue(43)).startsWith("Field of length 40"));
buf = (byte[])m.getObjectValue(62);
buf = m.getObjectValue(62);
exp = new byte[]{ 1, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef,
1, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd, (byte)0xef };
Assert.assertEquals(16, buf.length);
Assert.assertTrue(Arrays.equals(exp, buf));
buf = (byte[])m.getObjectValue(64);
Assert.assertEquals(16, buf.length);
Assert.assertTrue(Arrays.equals(exp, buf));
buf = (byte[])m.getObjectValue(63);
exp = new byte[]{ 0, (byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78, (byte)0x9a };
Assert.assertEquals(6, buf.length);
Assert.assertTrue(Arrays.equals(exp, buf));
buf = (byte[])m.getObjectValue(65);
Assert.assertEquals(6, buf.length);
Assert.assertTrue(Arrays.equals(exp, buf));
0x62, 1, (byte)0x23, (byte)0x45, (byte)0x67, (byte)0x89, (byte)0xab, (byte)0xcd };
Assert.assertArrayEquals(exp, buf);
buf = m.getObjectValue(64);
exp[8] = 0x64;
Assert.assertArrayEquals(exp, buf);
buf = m.getObjectValue(63);
exp = new byte[]{ 0, (byte)0x12, (byte)0x34, (byte)0x56, (byte)0x78, (byte)0x63 };
Assert.assertArrayEquals(exp, buf);
buf = m.getObjectValue(65);
exp[5] = 0x65;
Assert.assertArrayEquals(exp, buf);
}
@Test
@@ -68,12 +64,16 @@ public class TestBinaries {
//Create a message with both factories
IsoMessage ascii = mfactAscii.newMessage(0x600);
IsoMessage bin = mfactBin.newMessage(0x600);
Assert.assertFalse(ascii.isBinary() || ascii.isBinaryBitmap());
Assert.assertTrue(bin.isBinary());
//HEXencode the binary message, headers should be similar to the ASCII version
String hexBin = HexCodec.hexEncode(bin.writeData());
final byte[] _v = bin.writeData();
String hexBin = HexCodec.hexEncode(_v, 0, _v.length);
String hexAscii = new String(ascii.writeData()).toUpperCase();
Assert.assertEquals("0600", hexBin.substring(0, 4));
//Should be the same up to the field 42 (first 80 chars)
Assert.assertEquals(hexAscii.substring(0, 88), hexBin.substring(0, 88));
Assert.assertEquals(ascii.getObjectValue(43), new String(_v, 44, 40).trim());
//Parse both messages
byte[] asciiBuf = ascii.writeData();
IsoMessage ascii2 = mfactAscii.parseMessage(asciiBuf, 0);
@@ -98,7 +98,7 @@ public class TestBinaries {
byte[] sub1 = new byte[8];
System.arraycopy(data1, 16, sub1, 0, 8);
String sub2 = new String(data2, 16, 16, iso2.getCharacterEncoding());
Assert.assertEquals(sub2, HexCodec.hexEncode(sub1));
Assert.assertEquals(sub2, HexCodec.hexEncode(sub1, 0, sub1.length));
}
}
+7 -6
View File
@@ -6,6 +6,7 @@ import java.text.ParseException;
import java.util.Date;
import java.util.GregorianCalendar;
import com.solab.iso8583.IsoMessage;
import org.junit.*;
import com.solab.iso8583.IsoType;
@@ -20,11 +21,11 @@ import com.solab.iso8583.parse.Date4ParseInfo;
*/
public class TestDates {
private MessageFactory mf;
private MessageFactory<IsoMessage> mf;
@Before
public void init() throws IOException {
mf = new MessageFactory();
mf = new MessageFactory<IsoMessage>();
mf.setCharacterEncoding("UTF-8");
mf.setCustomField(48, new CustomField48());
mf.setConfigPath("config.xml");
@@ -39,12 +40,12 @@ public class TestDates {
today.set(GregorianCalendar.SECOND,0);
today.set(GregorianCalendar.MILLISECOND,0);
byte[] buf = IsoType.DATE4.format(soon).getBytes();
IsoValue<Date> comp = new Date4ParseInfo().parse(buf, 0, null);
IsoValue<Date> comp = new Date4ParseInfo().parse(0, buf, 0, null);
Assert.assertEquals(comp.getValue(), today.getTime());
//Now with the binary
ByteArrayOutputStream bout = new ByteArrayOutputStream();
comp.write(bout, true);
IsoValue<Date> bin = new Date4ParseInfo().parseBinary(bout.toByteArray(), 0, null);
IsoValue<Date> bin = new Date4ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
}
@@ -52,12 +53,12 @@ public class TestDates {
public void testDate10FutureTolerance() throws ParseException, IOException {
Date soon = new Date(System.currentTimeMillis() + 50000);
byte[] buf = IsoType.DATE10.format(soon).getBytes();
IsoValue<Date> comp = new Date10ParseInfo().parse(buf, 0, null);
IsoValue<Date> comp = new Date10ParseInfo().parse(0, buf, 0, null);
assert comp.getValue().after(new Date());
//Now with the binary
ByteArrayOutputStream bout = new ByteArrayOutputStream();
comp.write(bout, true);
IsoValue<Date> bin = new Date10ParseInfo().parseBinary(bout.toByteArray(), 0, null);
IsoValue<Date> bin = new Date10ParseInfo().parseBinary(0, bout.toByteArray(), 0, null);
Assert.assertEquals(comp.getValue().getTime(), bin.getValue().getTime());
}
+8 -3
View File
@@ -1,7 +1,6 @@
package j8583;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Test;
import com.solab.iso8583.util.HexCodec;
@@ -12,7 +11,7 @@ public class TestHexCodec {
public void encodeDecode(String hex) {
byte[] buf = HexCodec.hexDecode(hex);
Assert.assertEquals((hex.length() / 2) + (hex.length() % 2), buf.length);
String reenc = HexCodec.hexEncode(buf);
String reenc = HexCodec.hexEncode(buf, 0, buf.length);
if (reenc.startsWith("0") && !hex.startsWith("0")) {
Assert.assertEquals(reenc.substring(1), hex);
} else {
@@ -41,4 +40,10 @@ public class TestHexCodec {
encodeDecode("ABC");
}
@Test
public void testPartial() {
Assert.assertEquals("FF01", HexCodec.hexEncode(new byte[]{0, (byte)0xff, 1, 2, 3, 4},
1, 2));
}
}
+21 -5
View File
@@ -4,6 +4,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import com.solab.iso8583.IsoValue;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -18,11 +19,11 @@ import com.solab.iso8583.MessageFactory;
*/
public class TestIsoMessage {
private MessageFactory mf;
private MessageFactory<IsoMessage> mf;
@Before
public void init() throws IOException {
mf = new MessageFactory();
mf = new MessageFactory<IsoMessage>();
mf.setCharacterEncoding("UTF-8");
mf.setCustomField(48, new CustomField48());
mf.setConfigPath("config.xml");
@@ -102,15 +103,30 @@ public class TestIsoMessage {
IsoMessage iso1 = mf.newMessage(0x200);
IsoMessage iso2 = mf.newMessage(0x200);
assert iso1 != iso2;
assert iso1.getObjectValue(3) == iso1.getObjectValue(3);
assert iso1.getObjectValue(3).equals(iso2.getObjectValue(3));
assert iso1.getField(3) != iso2.getField(3);
assert iso1.getField(48) != iso2.getField(48);
CustomField48 cf48_1 = (CustomField48)iso1.getObjectValue(48);
CustomField48 cf48_1 = iso1.getObjectValue(48);
int origv = cf48_1.getValue2();
cf48_1.setValue2(origv + 1000);
CustomField48 cf48_2 = (CustomField48)iso2.getObjectValue(48);
CustomField48 cf48_2 = iso2.getObjectValue(48);
assert cf48_1 == cf48_2;
assert cf48_2.getValue2() == origv + 1000;
}
@Test(expected = IllegalArgumentException.class)
public void testSimpleFieldSetter() {
IsoMessage iso = mf.newMessage(0x200);
IsoValue<String> f3 = iso.getField(3);
iso.updateValue(3, "999999");
assert iso.getObjectValue(3).equals("999999");
IsoValue<String> nf3 = iso.getField(3);
assert f3 != nf3;
assert f3.getType() == nf3.getType();
assert f3.getLength() == nf3.getLength();
assert f3.getEncoder() == nf3.getEncoder();
iso.updateValue(4, "INVALID!");
throw new RuntimeException("Update failed!");
}
}
+65
View File
@@ -0,0 +1,65 @@
package j8583;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.MessageFactory;
import com.solab.iso8583.parse.ConfigParser;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.ParseException;
/**
* Tests for chochos/j8583#4.
*
* @author Enrique Zamudio
* Date: 18/01/13 10:13
*/
public class TestIssue4 {
@Test
public void testTextBitmap() throws IOException, ParseException {
MessageFactory<IsoMessage> tmf = new MessageFactory<IsoMessage>();
ConfigParser.configureFromClasspathConfig(tmf, "issue4.xml");
IsoMessage tm = tmf.newMessage(0x800);
final ByteBuffer bb = tm.writeToBuffer(2);
Assert.assertEquals("Wrong message length for new TXT",
70, bb.array().length);
Assert.assertEquals(68, bb.getShort());
MessageFactory<IsoMessage> tmfp = new MessageFactory<IsoMessage>();
ConfigParser.configureFromClasspathConfig(tmfp, "issue4.xml");
byte[] buf2 = new byte[bb.remaining()];
bb.get(buf2);
tm = tmfp.parseMessage(buf2, 0);
final ByteBuffer bbp = tm.writeToBuffer(2);
Assert.assertArrayEquals("Parsed-reencoded TXT differs from original",
bb.array(), bbp.array());
}
@Test
public void testBinaryBitmap() throws IOException, ParseException {
MessageFactory<IsoMessage> mf = new MessageFactory<IsoMessage>();
ConfigParser.configureFromClasspathConfig(mf, "issue4.xml");
IsoMessage bm = mf.getMessageTemplate(0x800);
bm.setBinaryBitmap(true);
final ByteBuffer bb = bm.writeToBuffer(2);
Assert.assertEquals("Wrong message length for new BIN", 62, bb.array().length);
Assert.assertEquals(60, bb.getShort());
MessageFactory<IsoMessage> mfp = new MessageFactory<IsoMessage>();
mfp.setUseBinaryBitmap(true);
ConfigParser.configureFromClasspathConfig(mfp, "issue4.xml");
byte[] buf2 = new byte[bb.remaining()];
bb.get(buf2);
bm = mfp.parseMessage(buf2, 0);
Assert.assertTrue("Parsed message should have binary bitmap flag set",
bm.isBinaryBitmap());
Assert.assertFalse(bm.isBinary());
final ByteBuffer bbp = bm.writeToBuffer(2);
Assert.assertArrayEquals("Parsed-reencoded BIN differs from original",
bb.array(), bbp.array());
}
}
+5 -4
View File
@@ -4,9 +4,10 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoValue;
import com.solab.iso8583.parse.NumericParseInfo;
import junit.framework.Assert;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -18,11 +19,11 @@ import com.solab.iso8583.MessageFactory;
*/
public class TestParsing {
private MessageFactory mf;
private MessageFactory<IsoMessage> mf;
@Before
public void init() throws IOException {
mf = new MessageFactory();
mf = new MessageFactory<IsoMessage>();
mf.setCharacterEncoding("UTF-8");
mf.setCustomField(48, new CustomField48());
mf.setConfigPath("config.xml");
@@ -102,7 +103,7 @@ public class TestParsing {
@Test
public void testBinaryNumberParsing() throws ParseException {
NumericParseInfo npi = new NumericParseInfo(6);
IsoValue<Number> val = npi.parseBinary(new byte[]{0x12, 0x34, 0x56}, 0, null);
IsoValue<Number> val = npi.parseBinary(0, new byte[]{0x12, 0x34, 0x56}, 0, null);
Assert.assertEquals(123456, val.getValue().intValue());
}
+4 -4
View File
@@ -43,10 +43,10 @@
<field num="41" type="BINARY" length="8">abcdef</field>
<field num="42" type="BINARY" length="4">abcde</field>
<field num="43" type="ALPHA" length="40">Field of length 40</field>
<field num="62" type="LLBIN">0123456789abcdef0123456789abcdef</field>
<field num="63" type="LLBIN">0123456789a</field>
<field num="64" type="LLLBIN">0123456789abcdef0123456789abcdef</field>
<field num="65" type="LLLBIN">0123456789a</field>
<field num="62" type="LLBIN">0123456789abcdef620123456789abcd</field>
<field num="63" type="LLBIN">01234567863</field>
<field num="64" type="LLLBIN">0123456789abcdef640123456789abcd</field>
<field num="65" type="LLLBIN">01234567865</field>
<field num="102" type="LLLVAR">Field of type LLLVAR</field>
</template>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE j8583-config PUBLIC "-//J8583//DTD CONFIG 1.0//EN"
"http://j8583.sourceforge.net/j8583.dtd">
<j8583-config>
<template type="0800">
<field num="3" type="NUMERIC" length="6">990000</field>
<field num="7" type="DATE10">1227120330</field>
<field num="11" type="NUMERIC" length="6">2</field>
<field num="24" type="NUMERIC" length="3">831</field>
<field num="41" type="ALPHA" length="8">42001</field>
<field num="42" type="ALPHA" length="15">420000000000001</field>
</template>
<parse type="0800">
<field num="3" type="NUMERIC" length="6" />
<field num="7" type="DATE10" />
<field num="11" type="NUMERIC" length="6" />
<field num="24" type="NUMERIC" length="3" />
<field num="41" type="ALPHA" length="8" />
<field num="42" type="ALPHA" length="15" />
</parse>
</j8583-config>
+4
View File
@@ -0,0 +1,4 @@
log4j.rootLogger=TRACE, A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d [%t] %-5p %C{1} - %m%n