Are you a Java Developer who needs to parse old times legacy
flat
files ? You're bored to write each time a parser for these files ? In this
case, you've come to the right page. Legacy4j
is a small Java library that
helps you load fixed length files to POJO and saves them the other way.
You can see the same detailed page on my website
So, let's get started.
That's just a JAR, put it in your classpath, and you're good to go.
Let's say you have this file to read data from.
Hello Adam 20100427
Bonjour Robert 20110912
BuongiornoMichele 20041108
Güten tag Karl 20100315
All you would have to do, is to define your Java object that will hold your data, and describe how data fits in it.
@FixedLengthRecord
public class Hello {
@FixedLengthField(10)
public String greeting;
@FixedLengthField(8)
public String name;
@DateField
public Calendar meetDate;
public String toString() {
return String.format("hello[%s, %s, %3$tY-%3$tm-%3$td]", greeting, name,
meetDate);
}
}
And then call the library and ask it gently to read the file data into your object.
public class HelloTest {
public static void main(String[] args) {
LegacyFile<Hello> file = LegacyFiles.openFileReader("file.txt", Hello.class);
for (Hello hello: file) {
System.out.println("> " + hello);
}
file.close();
}
}
That's it, you've read data from your file, with no need of any parser.
Here is the output of the Java program:
> hello[Hello , Adam , 2010-04-27]
> hello[Bonjour , Robert , 2011-09-12]
> hello[Buongiorno, Michele , 2004-11-08]
> hello[Güten tag , Karl , 2010-03-15]
##Field Annotations
The library makes huge use of Java annotations, that's how it knows how to parse the file, and cut the data where it should.
A file is made of records (lines), a Java object needs to have the
@FixedLengthRecord
annotation, so that the library knows how to work with it.
###FixedLengthField
The @FixedLengthField
annotation indicates that a field is delimited in size,
and its size can be set with it.
public @interface FixedLengthField {
/** @return the field length. */
public int value();
}
###DateField
The @DateField
annotation indicates that a field is a Date or a Calendar object,
and that the data read should be converted to the suitable destination, using a custom
date format.
public @interface DateField {
/** @return the date format. */
public String value() default "yyyyMMdd";
}
###DecimalField
The @DecimalField
annotation indicates that a field is one of int|Integer, double|Double,
float|Float or BigDecimal and that the data read should be converted to the suitable
destination, using a custom decimal format.
public @interface DecimalField {
/** @return the lenth of int and decimal value. */
public int[] value();
/** @return the separator if any (must be of length 0 or 1). */
public String separator() default "";
}
###CustomField
The @CustomField
annotation indicates that a field is of a custom type and that the data read should be converted to the suitable custom type, using a custom object mapper.
public @interface CustomField {
public Class<?> value();
}
##Field feature annotations
###TrimField
The @TrimField
indicates whether a field should be trimmed and in what direction (left, right or both which is the default value).
public @interface TrimField {
public enum Type { Both, Left, Right }
public Type value() defaults Type.Both;
}
###QuoteField
The @QuoteField
indicates whether a field should be quoted and with what characters ([]
, ()
, {}
, ''
or ""
which is the default value).
public @interface QuoteField {
public enum Type {
Brackets("[", "]"),
Parenthesis("(", ")"),
Braces("{", "}"),
Quote("\"", "\""),
SimpleQuote("'", "'");
public String before, after;
Type(String before, after) { this.before = before; this.after = after; }
}
public Type value() defaults Type.Quote;
}
- Awesome report sample data (ignored)
# ignored
0001[Alex ]12345678991234567,907820121215Alexandre Grison
0002[Foo ]12345673478751242,901220121223Foo bar bazz
Page 1
// ignore line starting with a # or a - or having only spaces then a Page number (report)
@FixedLengthRecord(ignoreMatching="^#.*|^-.*|^\\s+Page.*$", ignoreEmpty=true)
public class Item {
@FixedLengthField(4)
public int id;
@FixedLengthField(10)
@TrimField
@QuoteField(Type.Brackets)
public String name;
@DecimalField({8, 2}) // size = 8 + 2 = 10
public BigDecimal num1;
@DecimalField(value={7, 4}, separator=",") // size = 7 + 4 + 1 = 12
public Double num2;
@DateField("yyyyMMdd") // size = "yyyyMMdd".length() = 8
public Calendar cal;
@FixedLengthField(22)
@CustomField(PersonMapper.class)
public Person person;
public String toString() {
return String.format("Item [id=%s, name='%s', num1=%s, num2=%s, cal=%s, person=%s]",
id, name, num1, num2, new SimpleDateFormat("dd/MM/yyyy").format(cal.getTime()), person
);
}
public String format() {
return "";
}
}
public class Person {
public String firstName, surName;
public String toString() { return String.format("p[%s, %s]", firstName, surName); }
}
public class PersonMapper implements FieldMapper {
@Override
public int fill(Object inst, Field field, String value) {
try {
FixedLengthField flf = field.getAnnotation(FixedLengthField.class);
if (flf == null)
return 0;
if (field.getType() == Person.class) {
Person p = new Person();
p.firstName = value.substring(0, 10).trim();
p.surName = value.substring(10).trim();
field.set(inst, p);
}
return flf.value();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public String toString(Object inst, Field field) {
try {
Person p = (Person)field.get(inst);
return String.format("%-10s%-10s", p.firstName, p.surName);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
public class Test {
public static void main(String[] args) {
LegacyFile<Item> file = LegacyFiles.openFileReader("positionnal.txt", Item.class);
EngineMappers.registerMapper(Person.class, new PersonMapper());
for (Item item: file) {
System.out.println("> " + item);
System.out.println("= " + RecordConverter.toString(item));
}
file.close();
}
}
> Item [id=1, name='Alex', num1=12345678.99, num2=1234567.9078, cal=15/12/2012, person=p[Alexandre, Grison]]
= 0001[Alex ]12345678991234567,907820121215Alexandre Grison
> Item [id=2, name='Foo', num1=12345673.47, num2=8751242.9012, cal=23/12/2012, person=p[Foo bar ba, zz]]
= 0002[Foo ]12345673478751242,901220121223Foo bar bazz