Skip to content

Commit 4b5268b

Browse files
Implement FixedLenghtFileParser (#2)
Co-authored-by: Bruno Ortiz <brunortiz11@gmail.com>
1 parent 3ea1777 commit 4b5268b

File tree

17 files changed

+1631
-2
lines changed

17 files changed

+1631
-2
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.gradle
2+
.idea
3+
.kotlintest/
4+
build/
5+
*.iml

.travis.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
language: java
2+
3+
sudo: false
4+
5+
before_cache:
6+
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
7+
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
8+
9+
cache:
10+
directories:
11+
- "$HOME/.gradle/caches/"
12+
- "$HOME/.gradle/wrapper/"
13+
- "$HOME/.m2/repository/"
14+
15+
script: "./gradlew build --stacktrace"

README.md

Lines changed: 141 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,141 @@
1-
# fixed-length-file-handler
2-
Handlers for Fixed Length/Fixed Width files in a beautiful Kotlin DSL
1+
# Fixed Length File Handler
2+
3+
4+
5+
## Introduction
6+
When processing data from some systems (mainly legacy ones), it's usual to have Fixed Length Files, which are files that contain lines which content is split using a specific length for each field of a record.
7+
8+
This kind of files are sometimes tricky to handle as many times there is a spaghetti of string manipulations and padding, and character counting and... Well, many things to take care of.
9+
10+
This library comes to the rescue of programmers dealing with fixed length files. It enables you to simply define how your records are structured and it will handle these records for you in a nice Kotlin DSL for further processing.
11+
12+
## Using with Gradle
13+
14+
This library is published to `Bintray jcenter`, so you'll need to configure that in your repositories:
15+
```kotlin
16+
repositories {
17+
mavenCentral()
18+
jcenter()
19+
}
20+
```
21+
22+
And then you can import it into your dependencies:
23+
```kotlin
24+
dependencies {
25+
implementation("br.com.guiabolso:fixed-length-file-handler:{version}")
26+
}
27+
```
28+
29+
## Basic Usage
30+
31+
The basic usage assumes that you're reading a file with a single type of record.
32+
33+
Given a Fixed-Length File:
34+
35+
36+
#### Definition
37+
38+
| Field | Type | Initial Position | Final Position Exclusive |
39+
| ----- | ---- | ---------------- | ------------------------ |
40+
| UserName | String | 0 | 30 |
41+
| User Document | Int | 30 | 39 |
42+
| User Registry Date | LocalDate | 39 | 49 |
43+
44+
#### File
45+
46+
```
47+
FirstUsername 1234567892019-02-09
48+
SecondAndLongerUsername 9876543212018-03-10
49+
ThirdUsernameWithShorterDoc 0000001232017-04-11
50+
```
51+
52+
We can parse it with the `fixedLengthFileParser` DSL:
53+
54+
```kotlin
55+
data class MyUserRecord(val username: String, val userDoc: Int, val registryDate: LocalDate)
56+
57+
val fileInputStream: InputStream = getFileInputStream()
58+
59+
fixedLengthFileParser<MyUserRecord>(fileInputStream) {
60+
MyUserRecord(
61+
field(0, 30, Padding.PaddingRight(' ')),
62+
field(30, 39, Padding.PaddingLeft('0')),
63+
field(39, 49)
64+
)
65+
}
66+
```
67+
68+
The library is prepared to handle `Left Padding` and `Right Padding`. It's also prepared to handle many of Kotlin/Java types.
69+
70+
## Closing the file stream
71+
72+
**Attention** - You're responsible for closing the stream after processing the sequence, so be sure to close it!
73+
74+
## Default parsing
75+
76+
This library is prepared to handle some of the most usual Kotlin/Java types. More types may be added if they're required. The default types are:
77+
78+
- String
79+
- Int
80+
- Double
81+
- Long
82+
- Char
83+
- Boolean (Case insensitive)
84+
- LocalDate (Using default DateTimeFormatter)
85+
- LocalDateTime (Using default DateTimeFormatter)
86+
- BigDecimal
87+
88+
## Custom parsing
89+
90+
There might be times where the default types are not enough, and you need a custom parser for a given record.
91+
92+
For example: You know that a specific number contains a currency, and the last two digits are used for the cents.
93+
94+
This library is prepared to handle cases where you need custom parsing for a String, by modifying the `field` invocation:
95+
96+
```kotlin
97+
98+
// Parsing the field 0000520099 to 5200.99
99+
100+
field(15, 25, Padding.PaddingLeft('0')) { str: String -> StringBuilder(str).insert(str.length - 2, ".").toString().toBigDecimal() }
101+
```
102+
103+
## Advanced Usage
104+
105+
For an unknown reason, many Fixed-Length file providers use the same file for more than one record, denoting a specific bit for record identification, so there's a possibility that this happens:
106+
107+
```
108+
1 FirstUserName 123.12
109+
1 SecondUserName 002.50
110+
2 123456789 2019-02-09UserDocs
111+
2 000812347 2018-03-08AnotherUserDocs
112+
```
113+
114+
In this cases, the software must look at the first `char` to determine the record type. This situation is usually what leads to a spaghetti string manipulation. We can solve it by using this library's "advanced" options:
115+
116+
```kotlin
117+
data class FirstRecordType(username: String, userMoney: BigDecimal)
118+
data class SecondRecordType(userCode: Int, registerDate: LocalDate, docs: String)
119+
120+
fixedLengthFileParser<Any>(fileInputStream) {
121+
withRecord({ line -> line[0] == '1' }) {
122+
FirstRecordType(
123+
field(2, 22, Padding.PaddingRight(' ')),
124+
field(22, 28, Padding.PaddingLeft('0'))
125+
)
126+
}
127+
128+
withRecord( { line -> line[0] == '2' }) {
129+
SecondRecordType(
130+
field(2, 15, Padding.PaddingRight(' ')),
131+
field(15, 25),
132+
field(25, 40, Padding.PaddingRight(' '))
133+
)
134+
}
135+
}
136+
```
137+
138+
## Features
139+
140+
- The file is streamed into a sequence of values, and is never loaded in its entirety to the memory. You should expect this to have a good performance over a very big file.
141+
- The Kotlin DSL makes it easier to define the file parsing in a single point, and the sequence processing can be done anywhere

build.gradle.kts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import com.novoda.gradle.release.PublishExtension
2+
import org.jetbrains.dokka.gradle.DokkaTask
3+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
4+
5+
buildscript {
6+
repositories {
7+
mavenCentral()
8+
jcenter()
9+
}
10+
11+
dependencies {
12+
classpath("com.novoda:bintray-release:0.9.1")
13+
}
14+
}
15+
16+
plugins {
17+
kotlin("jvm") version "1.3.50"
18+
19+
`maven-publish`
20+
id("org.jetbrains.dokka") version "0.9.17"
21+
id("io.gitlab.arturbosch.detekt").version("1.1.1")
22+
}
23+
24+
apply(plugin = "com.novoda.bintray-release")
25+
26+
group = "br.com.guiabolso"
27+
version = "0.1.0"
28+
29+
repositories {
30+
mavenCentral()
31+
jcenter()
32+
}
33+
34+
dependencies {
35+
// Kotlin
36+
implementation(kotlin("stdlib-jdk8"))
37+
38+
// KotlinTest
39+
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2")
40+
}
41+
42+
tasks.withType<KotlinCompile> {
43+
kotlinOptions.jvmTarget = "1.8"
44+
}
45+
46+
tasks.withType<Test> {
47+
useJUnitPlatform()
48+
}
49+
50+
val sourcesJar by tasks.registering(Jar::class) {
51+
classifier = "sources"
52+
from(sourceSets.getByName("main").allSource)
53+
}
54+
55+
val javadocJar by tasks.registering(Jar::class) {
56+
val javadoc = tasks["dokka"] as DokkaTask
57+
javadoc.outputFormat = "javadoc"
58+
javadoc.outputDirectory = "$buildDir/javadoc"
59+
dependsOn(javadoc)
60+
classifier = "javadoc"
61+
from(javadoc.outputDirectory)
62+
}
63+
64+
detekt {
65+
toolVersion = "1.1.1"
66+
input = files("src/main/kotlin", "src/test/kotlin")
67+
}
68+
69+
publishing {
70+
publications {
71+
72+
register("maven", MavenPublication::class) {
73+
from(components["java"])
74+
artifact(sourcesJar.get())
75+
artifact(javadocJar.get())
76+
77+
pom {
78+
name.set("Fixed-Length-File-Handler")
79+
description.set("Fixed-Length-File-Handler")
80+
url.set("https://github.com/GuiaBolso/fixed-length-file-handler")
81+
82+
83+
scm {
84+
connection.set("scm:git:https://github.com/GuiaBolso/fixed-length-file-handler/")
85+
developerConnection.set("scm:git:https://github.com/GuiaBolso/")
86+
url.set("https://github.com/GuiaBolso/fixed-length-file-handler")
87+
}
88+
89+
licenses {
90+
license {
91+
name.set("The Apache 2.0 License")
92+
url.set("https://opensource.org/licenses/Apache-2.0")
93+
}
94+
}
95+
}
96+
}
97+
}
98+
}
99+
100+
configure<PublishExtension> {
101+
artifactId = "fixed-length-file-handler"
102+
autoPublish = true
103+
desc = "Fixed Length File Handler"
104+
groupId = "br.com.guiabolso"
105+
userOrg = "gb-opensource"
106+
setLicences("APACHE-2.0")
107+
publishVersion = version.toString()
108+
uploadName = "Fixed-Length-File-Handler"
109+
website = "https://github.com/GuiaBolso/fixed-length-file-handler"
110+
setPublications("maven")
111+
}

0 commit comments

Comments
 (0)