-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implemented simple read and write operations
- Loading branch information
Showing
14 changed files
with
816 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,3 +15,4 @@ buildNumber.properties | |
.project | ||
# JDT-specific (Eclipse Java Development Tools) | ||
.classpath | ||
/.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,75 @@ | ||
# flat-file-parser | ||
Implementation of a parser for flat file databases. | ||
|
||
Flat file parser is a small java project that allows developers to parse and write flat files in a simple an | ||
concise manner. | ||
|
||
Provides an Interface to parse flat files and map its contents into java objects. | ||
To represent and process the flat file definition/structure the project follows declarative approach make use of | ||
annotations. Also allows to create flat files using the same definition as when parsing existing files. | ||
|
||
## Prerequisites | ||
Before you begin, ensure you have met the following requirements: | ||
* You can support Java 17 | ||
* Since this project is not published to the central repository, make sure you can add .jar files to your classpath | ||
|
||
## 'Installing' | ||
|
||
To install flat file parser, follow these steps: | ||
Download the latest *.jar release from the release tab and add the downloaded .jar to your classpath | ||
|
||
## Using flat-file-parser | ||
|
||
To use flat-file-parser, follow these steps: | ||
* Create a Class with fields specifying the record format | ||
* Use the factory to create a parser instance | ||
* call `parse` to parse a flat file and map its data into a list of objects | ||
|
||
```java | ||
import cphne.flatfileparser.Field; | ||
import cphne.flatfileparser.FlatFileParser; | ||
import cphne.flatfileparser.ParserFactory; | ||
|
||
import java.nio.file.Path; | ||
|
||
class Main() { | ||
|
||
static class DataObject { | ||
@Field(start = 0, end = 3) | ||
private int age; | ||
|
||
@Field(start = 3, end = 10) | ||
private String name; | ||
|
||
// getters and setters... | ||
} | ||
|
||
public static void main(String[] args) { | ||
FlatFileParser parser = ParserFactory.newInstance(); | ||
List<DataObject> data = parser.parse(Path.of("path/to/your/flatfile"), DataObject.class); | ||
// process read data... | ||
} | ||
} | ||
``` | ||
|
||
## Contributing to flat-file-parser | ||
This is my first 'public' project on GitHub, I would be grateful for any constructive feedback. Should you have | ||
suggestions about features, see the contact section of this readme. | ||
|
||
For a general guideline, to contribute to flat-file-parser, follow these steps: | ||
|
||
1. Fork this repository. | ||
2. Create a branch: `git checkout -b <branch_name>`. | ||
3. Make your changes and commit them: `git commit -m '<commit_message>'` | ||
4. Push to the original branch: `git push origin <project_name>/<location>` | ||
5. Create the pull request. | ||
|
||
|
||
## Contact | ||
|
||
If you want to contact me you can reach me at [email protected] | ||
|
||
## License | ||
<!--- If you're not sure which open license to use see https://choosealicense.com/---> | ||
|
||
This project uses the following license: [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>com.github.cphne</groupId> | ||
<artifactId>flat-file-parser</artifactId> | ||
<version>1.0.0</version> | ||
|
||
<properties> | ||
<maven.compiler.source>17</maven.compiler.source> | ||
<maven.compiler.target>17</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
<dependencies> | ||
<dependency> | ||
<groupId>ch.qos.logback</groupId> | ||
<artifactId>logback-core</artifactId> | ||
<version>1.4.11</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>ch.qos.logback</groupId> | ||
<artifactId>logback-classic</artifactId> | ||
<version>1.4.11</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.projectlombok</groupId> | ||
<artifactId>lombok</artifactId> | ||
<version>1.18.28</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter</artifactId> | ||
<version>5.9.3</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.assertj</groupId> | ||
<artifactId>assertj-core</artifactId> | ||
<version>3.24.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package cphne.flatfileparser; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Represents the definition of a field for a record in a flat file | ||
* <p> | ||
* Define the start and end positions of a field. Is required by {@link FlatFileParser} to parse records of a flat file | ||
* and the referenced fields. | ||
*/ | ||
@Target(ElementType.FIELD) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface Field { | ||
/** | ||
* | ||
* @return the start position of a field | ||
*/ | ||
int start(); | ||
|
||
/** | ||
* | ||
* @return the end position of a field | ||
*/ | ||
int end(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package cphne.flatfileparser; | ||
|
||
import java.io.*; | ||
import java.nio.file.Path; | ||
import java.util.List; | ||
|
||
/** | ||
* Declares operations for parsing and writing of files formatted as a flat file | ||
* <p> | ||
* Parse functionality supports multiple variants of input types. Parses the file and maps its content into Objects | ||
* of a provided type. The flat file format is specified in the Class the content should be mapped into via the | ||
* {@link Field} annotation. | ||
* <p> | ||
* Transferring/writing objects to a flat file works similar to the parse operations. The Class of objects that | ||
* should be written is required to provide the flat file format via the {@link Field} annotation. | ||
*/ | ||
public interface FlatFileParser { | ||
|
||
/** | ||
* Parse a file and map the contained data to Objects | ||
* | ||
* @param file the file which contents to parse | ||
* @param clazz the type the data should be mapped to | ||
* @return {@code List<T>} the list of objects the data was mapped to | ||
* @param <T> the type of objects in which the data will be mapped | ||
* @throws IOException if the file cant be read or opened | ||
* @throws ParserException if the parser cant parse the file | ||
*/ | ||
<T> List<T> parse(File file, Class<T> clazz) throws IOException, ParserException; | ||
|
||
/** | ||
* Parse a file and map the contained data to Objects | ||
* | ||
* @param path the path to a file which contents to parse | ||
* @param clazz the type the data should be mapped to | ||
* @return {@code List<T>} the list of objects the data was mapped to | ||
* @param <T> the type of objects in which the data will be mapped | ||
* @throws IOException if the file cant be read or opened | ||
* @throws ParserException if the parser cant parse the file | ||
*/ | ||
<T> List<T> parse(Path path, Class<T> clazz) throws IOException, ParserException; | ||
|
||
/** | ||
* Parse data of an InputStream and map the contained data to Objects | ||
* | ||
* @param inputStream the stream which contains the contents to parse | ||
* @param clazz the type the data should be mapped to | ||
* @return {@code List<T>} the list of objects the data was mapped to | ||
* @param <T> the type of objects in which the data will be mapped | ||
* @throws IOException if the stream cant be read | ||
* @throws ParserException if the parser cant parse the file | ||
*/ | ||
<T> List<T> parse(InputStream inputStream, Class<T> clazz) throws IOException, ParserException; | ||
|
||
/** | ||
* Parse a content of a BufferedReader and map the contained data to Objects | ||
* | ||
* @param reader the reader which contents to parse | ||
* @param clazz the type the data should be mapped to | ||
* @return {@code List<T>} the list of objects the data was mapped to | ||
* @param <T> the type of objects in which the data will be mapped | ||
* @throws IOException if the reader cant be read | ||
* @throws ParserException if the parser cant parse the file | ||
*/ | ||
<T> List<T> parse(BufferedReader reader, Class<T> clazz) throws IOException, ParserException; | ||
|
||
/** | ||
* Convert a list of Objects to flat file | ||
* | ||
* @param stream OutputStream to write the data to | ||
* @param data List of objects to convert | ||
* @param <T> the type of objects which will be converted | ||
* @throws IOException if the data cant be written to the stream | ||
* @throws ParserException if the parser cant convert the objects to the specified flat file format | ||
*/ | ||
<T> void write(OutputStream stream, List<T> data) throws IOException, ParserException; | ||
} |
144 changes: 144 additions & 0 deletions
144
src/main/java/cphne/flatfileparser/FlatFileParserImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package cphne.flatfileparser; | ||
|
||
import java.io.*; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
/** | ||
* Implementation of the {@link FlatFileParser} interface | ||
*/ | ||
class FlatFileParserImpl implements FlatFileParser { | ||
|
||
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FlatFileParserImpl.class); | ||
|
||
/** | ||
* The character to use to remaining space of a field | ||
*/ | ||
private final String padCharacter; | ||
|
||
/** | ||
* Default constructor. Defines {@code " "} as the default {@link FlatFileParserImpl#padCharacter} | ||
*/ | ||
public FlatFileParserImpl() { | ||
this.padCharacter = " "; | ||
} | ||
|
||
/** | ||
* | ||
* @param padCharacter the Character to use for padding fields | ||
*/ | ||
public FlatFileParserImpl(String padCharacter) { | ||
this.padCharacter = padCharacter; | ||
} | ||
|
||
@Override | ||
public <T> List<T> parse(File file, Class<T> clazz) throws IOException, ParserException { | ||
return parse(file.toPath(), clazz); | ||
} | ||
|
||
@Override | ||
public <T> List<T> parse(Path path, Class<T> clazz) throws IOException, ParserException { | ||
try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { | ||
return parse(bufferedReader, clazz); | ||
} | ||
} | ||
|
||
@Override | ||
public <T> List<T> parse(InputStream inputStream, Class<T> clazz) throws IOException, ParserException { | ||
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { | ||
return parse(bufferedReader, clazz); | ||
} | ||
} | ||
|
||
@Override | ||
public <T> List<T> parse(BufferedReader reader, Class<T> clazz) throws IOException, ParserException { | ||
List<T> targets = new ArrayList<>(); | ||
String line = reader.readLine(); | ||
while (line != null && !line.isBlank()) { | ||
log.debug("Parsing line {}", line); | ||
try { | ||
targets.add(parse(line, clazz)); | ||
} catch (ReflectiveOperationException e) { | ||
throw new ParserException(e); | ||
} | ||
line = reader.readLine(); | ||
} | ||
return targets; | ||
} | ||
|
||
private <T> T parse(String line, Class<T> clazz) throws ReflectiveOperationException, ParserException { | ||
LineParser<T> lineParser = new LineParserImpl<>(line, clazz.getDeclaredConstructor().newInstance()); | ||
List<java.lang.reflect.Field> fields = getFields(clazz); | ||
if (fields.isEmpty()) { | ||
throw new ParserException("Cant parse data, no field definitions defined for class %s.".formatted(clazz)); | ||
} | ||
for (java.lang.reflect.Field field : fields) { | ||
lineParser.parse(field); | ||
} | ||
return lineParser.getTarget(); | ||
} | ||
|
||
private static <T> List<java.lang.reflect.Field> getFields(Class<T> clazz) { | ||
return Arrays.stream(clazz.getDeclaredFields()) | ||
.filter(f -> f.isAnnotationPresent(Field.class)) | ||
.toList(); | ||
} | ||
|
||
|
||
@Override | ||
public <T> void write(OutputStream stream, List<T> dataList) throws IOException, ParserException { | ||
if (dataList.isEmpty()) { | ||
log.warn("Provided data to is empty, there is nothing to convert or write."); | ||
} | ||
for (T concreteObject : dataList) { | ||
List<java.lang.reflect.Field> fields = getFields(concreteObject.getClass()); | ||
StringBuilder row = new StringBuilder(); | ||
for (java.lang.reflect.Field field : fields) { | ||
try { | ||
row.append(computeColumn(concreteObject, field)); | ||
} catch (ReflectiveOperationException e) { | ||
throw new ParserException(e); | ||
} | ||
} | ||
row.append("%n".formatted()); | ||
stream.write(row.toString().getBytes(StandardCharsets.UTF_8)); | ||
} | ||
} | ||
|
||
private <T> String computeColumn( | ||
T concreteObject, | ||
java.lang.reflect.Field field | ||
) throws IllegalAccessException, InvocationTargetException { | ||
Method getter = findGetter(field, concreteObject); | ||
String data = getter.invoke(concreteObject).toString(); | ||
return data + padding(field, data.length()); | ||
} | ||
|
||
private String padding(java.lang.reflect.Field field, int dataLength) { | ||
int fieldLength = field.getAnnotation(Field.class).end() - field.getAnnotation(Field.class).start(); | ||
int unusedSpaceLength = fieldLength - dataLength; | ||
return padCharacter.repeat(unusedSpaceLength); | ||
} | ||
|
||
private <T> Method findGetter(java.lang.reflect.Field field, T instance) { | ||
Method setter = Arrays.stream(instance.getClass().getMethods()) | ||
.filter(m -> Modifier.isPublic(m.getModifiers())) | ||
.filter(m -> m.getName().startsWith("get")) | ||
.filter(m -> m.getName() | ||
.equals("get%s%s".formatted(field.getName().substring(0, 1).toUpperCase(), | ||
field.getName().substring(1) | ||
))) | ||
.findFirst() | ||
.orElseThrow(); | ||
log.debug("Found getter for field {} with name {}.", field.getName(), setter.getName()); | ||
return setter; | ||
} | ||
|
||
} |
Oops, something went wrong.