Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions persons.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name,score
Alex,"[A, B, A]"
Maria,"[A, A, A]"
John,"[B, C, B]"
Elena,"[A, B, C]"
Victor,"[B, B, B]"
Anna,"[A, C, B]"
Daniel,"[C, B, A]"
Sofia,"[A, A, B]"
Mihai,"[B, C, C]"
Laura,"[A, B, A]"
Alex,"[A, B, A]"
Maria,"[A, A, A]"
John,"[B, C, B]"
Elena,"[A, B, C]"
Victor,"[B, B, B]"
Anna,"[A, C, B]"
Daniel,"[C, B, A]"
Sofia,"[A, A, B]"
Mihai,"[B, C, C]"
Laura,"[A, B, A]"
24 changes: 23 additions & 1 deletion src/main/java/org/writer/Main.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
package org.writer;

import org.writer.csv.CsvFileWriter;
import org.writer.model.Person;
import org.writer.model.Student;

import java.util.List;

public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
String peopleFileName = "persons.csv";
String studentshipName = "students.csv";

List<Person> personList = Person.getTestList();
List<Student> studentList = Student.getTestList();


CsvFileWriter csvFileWriter = new CsvFileWriter();

csvFileWriter.writeToFile(studentList, peopleFileName);
csvFileWriter.writeToFile(studentList, studentshipName);


}

{

}
}
58 changes: 58 additions & 0 deletions src/main/java/org/writer/csv/CsvFileWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.writer.csv;

import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.Pipeline;
import org.writer.csv.pipeline.steps.*;
import org.writer.exception.ArrayIsEmptyException;

import java.util.List;


/**
* Writes Java objects to a CSV file using reflection.
*
* <p>The writer automatically generates a CSV header based on
* the object's fields and ensures header consistency when appending data.</p>
*
* <p>This class is not thread-safe.</p>
*/
public class CsvFileWriter implements Writable {
private Pipeline<CsvContext> writeToCsvFilePipeline;
private CsvContext csvContext;


/**
* Writes a list of objects to a CSV file.
*
* <p>All objects in the list must be of the same type. The CSV header
* is generated automatically using reflection.</p>
*
* @param data list of objects to be written; must not be empty and must contain
* objects of the same type
* @param fileName name of the target CSV file
* @throws ArrayIsEmptyException if the data list is empty
* @throws IllegalArgumentException if the list contains objects of different types
* @throws IllegalStateException if an existing CSV header does not match
*/
@Override
public void writeToFile(List<?> data, String fileName) {
this.csvContext = new CsvContext(data, fileName);
this.writeToCsvFilePipeline = buildPipeline();
writeToCsvFilePipeline.execute(csvContext);
}


private Pipeline<CsvContext> buildPipeline() {
Pipeline<CsvContext> writeToCsvFilePipeline = new Pipeline<>();

writeToCsvFilePipeline.addStep(new ValidateDataStep());
writeToCsvFilePipeline.addStep(new CheckFileExistsStep());

writeToCsvFilePipeline.addStep(new ResolveMetadataStep());

writeToCsvFilePipeline.addStep(new GenerateHeaderStep());
writeToCsvFilePipeline.addStep(new WriteDataToCsvFileStep());

return writeToCsvFilePipeline;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.writer;
package org.writer.csv;

import java.util.List;

public interface Writable {
public interface Writable{

void writeToFile(List<?> data, String fileName);

Expand Down
4 changes: 4 additions & 0 deletions src/main/java/org/writer/csv/context/ContextInterface.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.writer.csv.context;

public interface ContextInterface {
}
29 changes: 29 additions & 0 deletions src/main/java/org/writer/csv/context/CsvContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.writer.csv.context;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.List;

@Getter
@Setter
//@Builder
public class CsvContext implements ContextInterface {
private List<?> data;
private Path path;
private boolean isFileExists = false;
private Class<?> clazz;
private Field[] fields;
private String header;

public CsvContext(List<?> data, String fileName) {
this.data = data;
this.path = Path.of(fileName);

}


}
26 changes: 26 additions & 0 deletions src/main/java/org/writer/csv/pipeline/Pipeline.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.writer.csv.pipeline;

import org.writer.csv.context.ContextInterface;

import java.util.LinkedList;
import java.util.List;

public class Pipeline<TContext extends ContextInterface> {
private final List<PipelineStep<TContext>> steps = new LinkedList<>();

public void execute(TContext context) {
steps.forEach(pipelineStep -> pipelineStep.execute(context));
}

public void addStep(PipelineStep<TContext> pipelineStep) {
steps.add(pipelineStep);
}
public void addStepByCondition(boolean condition, PipelineStep<TContext> firstStep, PipelineStep<TContext> secondStep){
if (condition){
steps.add(firstStep);
}else {
steps.add(secondStep);
}
}

}
7 changes: 7 additions & 0 deletions src/main/java/org/writer/csv/pipeline/PipelineStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.writer.csv.pipeline;

import org.writer.csv.context.ContextInterface;

public interface PipelineStep<TContext extends ContextInterface> {
void execute(TContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.writer.csv.pipeline.steps;

import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.PipelineStep;

import java.io.IOException;
import java.nio.file.Files;

public class CheckFileExistsStep implements PipelineStep<CsvContext> {
/**
* Checks whether the target CSV file exists and is not empty.
* Stores the result in the CsvContext.
*
* @param csvContext context containing the path to the CSV file
* @throws RuntimeException if an I/O error occurs while checking the file
*/

@Override
public void execute(CsvContext csvContext) {
try {
boolean isFileExists = Files.exists(csvContext.getPath()) && Files.size(csvContext.getPath()) > 0;
csvContext.setFileExists(isFileExists);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.writer.csv.pipeline.steps;

import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.PipelineStep;
import org.writer.exception.CsvHeaderMismatchException;

import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.stream.Collectors;


public class GenerateHeaderStep implements PipelineStep<CsvContext> {
/**
* Generates a CSV header based on the model fields and stores it in the context.
* If the CSV file already exists, validates that the generated header matches
* the header stored in the file.
*
* @param csvContext context containing file path and model metadata
* @throws CsvHeaderMismatchException if the existing file header does not match the generated one
* @throws RuntimeException if an I/O error occurs while reading the file
*/
@Override
public void execute(CsvContext csvContext) {

csvContext.setHeader(Arrays.stream(csvContext.getFields())
.map(Field::getName)
.collect(Collectors.joining(",")));

if (csvContext.isFileExists()) {
try (BufferedReader reader = Files.newBufferedReader(csvContext.getPath())) {
String fileHeader = reader.readLine();

if (!fileHeader.equals(csvContext.getHeader())) {
throw new CsvHeaderMismatchException(
"CSV header mismatch. Expected: "
+ csvContext.getHeader()
+ ", Actual: "
+ fileHeader
);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.writer.csv.pipeline.steps;

import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.PipelineStep;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;

public class ResolveMetadataStep implements PipelineStep<CsvContext> {
/**
* Resolves metadata required for CSV generation.
* Determines the model class from the data list, extracts its fields,
* sorts them by name and makes them accessible.
*
* @param csvContext context containing the data to be written
* @throws IndexOutOfBoundsException if the data list is empty
*/

@Override
public void execute(CsvContext csvContext) {
csvContext.setClazz(csvContext.getData().get(0).getClass());

Field[] fields = csvContext.getClazz().getDeclaredFields();
Arrays.sort(fields, Comparator.comparing(Field::getName));
for (Field f : fields) {
f.setAccessible(true);
}
csvContext.setFields(fields);
}
}
20 changes: 20 additions & 0 deletions src/main/java/org/writer/csv/pipeline/steps/ValidateDataStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.writer.csv.pipeline.steps;

import org.writer.csv.context.CsvContext;
import org.writer.csv.pipeline.PipelineStep;
import org.writer.exception.ArrayIsEmptyException;

public class ValidateDataStep implements PipelineStep<CsvContext> {
/**
* Validates that the data list is not empty.
*
* @param csvContext context containing the data to be written
* @throws ArrayIsEmptyException if the data list is empty
*/
@Override
public void execute(CsvContext csvContext) {
if (csvContext.getData().isEmpty()) {
throw new ArrayIsEmptyException("Fill array with data!");
}
}
}
Loading