Skip to content

Latest commit

 

History

History
308 lines (221 loc) · 13.5 KB

README.template.md

File metadata and controls

308 lines (221 loc) · 13.5 KB

Build Status License Version security status stability status

What's JRecordBind?

A tiny and super fast library that aims to:

  • parse fixed or variable length text files and map them to java beans (Unmarshaller)
  • export java beans to fixed or variable length text files (Marshaller)

Why?

Almost everybody has written an import procedure of some sort.

Fixed-length are a must for every public institution (at least in Italy): regardless of the age of the destination system, everyone can read a plain text file.

JRecordBind aims to leverage the boring parsing task and let the developer focus on the real problem: understanding the data and find a way to feed the persistence layer.

What's a record?

A record is a structured text, a line usually, which contains typed information. For example:

JOHN                SMITH               ABCDEF88L99H123B1979051800000000811804                  197Y

This record starts with a "name" property from column 0 to column 20, right padded with spaces. Then it has a "surname" property, equally padded, from 21 to 40.

Second to last property is a float property from column 89 to 99, left padded with spaces, where the last 2 digits are the decimal part: its value is 1.97

It ends with a boolean property, where "Y" means "true" and "N" means "false".

Advantages

JRecordBind is (AFAIK) the only tool aimed at fixed-length files that's able to marshall (write) and unmarshall (read). By the way you may be a producer of fixed-length files, not just a consumer.

JRecordBind supports hierarchical fixed-length files: records of some type that are used only after parent record types.

JRecordBind uses XML Schema to define the file format: that could make JRecordBind quickier to learn.

Which Java?

Since version 3.0.0, JRecordBind requires Java 11+.

Your application module-info.java file needs to require org.fissore.jrecordbind and java.xml.bind, and to export generated classes to JRecordBind, like so:

module com.mycompany {

  requires org.fissore.jrecordbind;
  requires java.xml.bind;

  exports com.mycompany.generated to org.fissore.jrecordbind;

}

Maven

Maven users can add JRecordBind as a dependency

<dependency>
  <groupId>org.fissore.jrecordbind</groupId>
  <artifactId>jrecordbind</artifactId>
  <version>@@VERSION@@</version>
</dependency>

Support

If you need support, drop me an email. If you have found a bug, please report it! report it now!

Is it good?

Yes, it is.

Show me!

Take a look at the example project, read the how-tos below, and play with the tests.

How does it work?

Record definition

When importing a fixed-length file, someone has provided a thorough documentation regarding how the file is structured: each property, their length, their value and how to convert them.

JRecordBind needs that documentation: it's the starting point. Your task is to write an XML Schema version of it. Here's an example:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://schemas.jrecordbind.org/jrb/simple" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.jrecordbind.org/jrb/simple" xmlns:jrb="http://jrecordbind.org/3/xsd" elementFormDefault="qualified">
  <xs:complexType name="SimpleRecord">
    <xs:sequence>
      <xs:element name="name" type="xs:string" jrb:length="20"/>
      <xs:element name="surname" type="xs:string" jrb:length="20"/>
      <xs:element name="taxCode" type="xs:string" jrb:length="16"/>
      <xs:element name="birthday" type="xs:date" jrb:length="8" jrb:converter="org.fissore.jrecordbindtests.test.TestConverters$SimpleRecordDateConverter"/>
      <xs:element name="oneInteger" type="xs:int" jrb:length="10" jrb:padder="org.fissore.jrecordbind.padders.ZeroLeftPadder"/>
      <xs:element name="twoInteger" type="xs:int" jrb:length="15" jrb:padder="org.fissore.jrecordbind.padders.SpaceRightPadder"/>
      <xs:element name="oneFloat" type="xs:float" jrb:length="10" jrb:converter="org.fissore.jrecordbindtests.test.TestConverters$SimpleRecordFloatConverter" jrb:padder="org.fissore.jrecordbind.padders.SpaceLeftPadder"/>
      <xs:element name="selected" type="xs:boolean" jrb:length="1" jrb:converter="org.fissore.jrecordbindtests.test.TestConverters$YNBooleanConverter"/>
    </xs:sequence>
  </xs:complexType>
  <xs:element name="main" type="SimpleRecord" jrb:length="120"/>
</xs:schema>

It's a standard XML Schema file (xsd) plus some special jrb attributes and a mandatory main element.

The main element will be the starting point, the main bean JRecordBean will read/write.

Here's the list of jrb attributes:

ATTRIBUTE SCOPE MEANING MANDATORY
jrb:length main element The length of the fixed-length file Yes, unless "delimiter" is specified. When "length" is specified, it's a fixed-length file. When "length" is not specified, it's a dynamic-length file
jrb:length other elements The length of that particular property Yes, unless the file is a dynamic-length file
jrb:delimiter main element What delimits each property No, unless it's dynamic-length file
jrb:padder main element The default padder to use when not otherwise specified on elements (see below) No. JRecordBind will use its default (right padding with spaces)
jrb:padder other elements A custom padder for that property No. JRecordBind will use the default one (see above)
jrb:lineSeparator main element What ends each line in the file No. By default a "new line" char will be used. DOS format files can be achieved using the value "&#13;&#10;"
jrb:converter other elements How to convert that field to/from a string. Elements with types xs:string, xs:int, and xs:long are automatically converted. No. JRecordBind will treat the property as a string
jrb:row other elements If a record is split into more than one line, from the second line on, specify the line number (zero based) No. JRecordBind will default it to 0: the whole record on one line. See the multi-row.def.xsd example
jrb:subclass xs:complexType tag If you need to extend/override some generated class, you can make JRecordBind instantiate the specified class instead of the generated one. The specified class must extend the generated class. See the enum.def.xsd example No. JRecordBind will default to the generated class
jrb:setter xs:choice declaration When using JAXB bindings.xjb with the "globalBindings choiceContentProperty='true'", specify the name of the method JAXB has generated Yes, if using choiceContentProperty='true' in bindings.xjb

When the definition is ready, generate the beans: use JAXB2 Maven plugin (for an example configuration, give a look at the test pom.xml).

Congratulations! You are now ready to read/write fixed-length files.

A note on dates

When an element is of type xs:date, by default JAXB will generate a class field of type XMLGregorianCalendar, which JRecordBind does not support. It's mandatory to use a minimal bindings.xjb file in order to make JAXB generate Calendar fields, like this one:

<bindings xmlns="http://java.sun.com/xml/ns/jaxb" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <globalBindings>
    <javaType name="java.util.Calendar" xmlType="xs:date" parseMethod="javax.xml.bind.DatatypeConverter.parseDate" printMethod="javax.xml.bind.DatatypeConverter.printDate"/>
    <javaType name="java.util.Calendar" xmlType="xs:dateTime" parseMethod="javax.xml.bind.DatatypeConverter.parseDate" printMethod="javax.xml.bind.DatatypeConverter.printDate"/>
  </globalBindings>
</bindings>

Take a look at the example project.

Read/Unmarshall

Given an fixed-length text file, honoring the definition, for example

WALTER              LIPPMANN            ABCDEF79D18K999A1889092381197
DAVID               JOHNSON             ABCDEF79E18S999B1889092381197

you can read/unmarshall it this way:

Unmarshaller<SimpleRecord> unmarshaller = new Unmarshaller<SimpleRecord>(
    new InputStreamReader(getClass().getResourceAsStream("/simple.def.xsd")));

Iterator<SimpleRecord> iter = unmarshaller.unmarshall(
    new InputStreamReader(getClass().getResourceAsStream("simple_test.txt")));

assertTrue(iter.hasNext());

SimpleRecord record = iter.next();
assertEquals("WALTER", record.getName());
assertEquals("LIPPMANN", record.getSurname());
assertEquals("ABCDEF79D18K999A", record.getTaxCode());

Write/Marshall

Given a bean loaded with data, you can write/marshall it this way:

SimpleRecord record = new SimpleRecord();
record.setName("WALTER");
record.setSurname("LIPPMANN");
// other properties omitted

Marshaller<SimpleRecord> marshaller = new Marshaller<SimpleRecord>(
    new InputStreamReader(getClass().getResourceAsStream("/simple.def.xsd")));

Writer writer = new StringWriter();

marshaller.marshall(record, writer);

System.out.println(writer.toString());

How-tos

Different record types

Hierarchical fixed-length files use ID fields to distinguish the records. Documentation will say something like "Record 000 is an address, records A01 are vehicles..." and so on.

JRecordBind can easily recognize each record type using the XML Schema standard attribute fixed: see this example.

Dynamic length files

You can omit the jrb:length attribute and instead specify the jrb:delimiter attribute: this way you get a dynamic-length file: see this example.

Extend the generated bean

Add the jrb:subclass attribute to the xs:complexType tag. By specifying the fully qualified name of a class extending the generated class, JRecordBind will instantiate that class instead of the generated one, allowing you to extend/override the generated class: see this example.

Using xs:choice with choiceContentProperty='true'

Add the jrb:setter attribute to the xs:choice tag.

With the types One and Two inside an xs:choice element, the default generated class will have methods setOne and setTwo. JAXB can generate only one method by specifying choiceContentProperty=true in file bindings.xjb: the generated method will be setOneOrTwo.

JRecordBind is not aware of this JAXB trick, it will look for methods setOne and setTwo and it will fail: that information must be duplicated into the XML Schema, in a way JRecordBind can understand.

Add the attribute jrb:setter="oneOrTwo" to the xs:choice tag, and JRecordBind will work as expected.

See this example.

Fine grained control on file reading when unmarshalling

When the Unmarshaller reads from the file, by default it returns the current line.

In order to customize such behaviour, create an Unmarshaller passing an implementation of the LineReader interface: see this example.

Use a custom line separator (and reading/writing DOS format files)

Line separator can be customized using the attribute jrb:lineSeparator. By default, lines will be separated by a "new line" char (\n). If order to read/write DOS format files, specify the attribute this way

jrb:lineSeparator="&#13;&#10;"

which is the XML equivalent of \r\n.

Other examples

Each feature of JRecordBind has at least one xsd file that tests it.

Take a look at the repository.