style(code): unify formatting across io and search components

This commit is contained in:
2026-09-02 13:14:28 +04:00
parent d7f4d30cc3
commit 1ec1d68a02
8 changed files with 261 additions and 250 deletions
@@ -13,22 +13,21 @@ import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord; import org.apache.commons.csv.CSVRecord;
import ru.project.student.Student; import ru.project.student.Student;
import ru.project.student.StudentBuilder; import ru.project.student.StudentBuilder;
public class StudentCsvReader implements Iterator<Student>, AutoCloseable {
public class StudentCsvReader implements Iterator<Student>, AutoCloseable {
private static final String GROUP_NUMBER = "groupNumber"; private static final String GROUP_NUMBER = "groupNumber";
private static final String AVERAGE_GRADE = "averageGrade"; private static final String AVERAGE_GRADE = "averageGrade";
private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; private static final String RECORD_BOOK_NUMBER = "recordBookNumber";
private final CSVParser parser; private final CSVParser parser;
private final Iterator<CSVRecord> records; private final Iterator<CSVRecord> records;
private int lineOfFile=0; private int lineOfFile = 0;
public StudentCsvReader(Path path) { public StudentCsvReader(Path path) {
if (!path.isAbsolute()) { if (!path.isAbsolute()) {
throw new IllegalArgumentException("File path must be absolute"); throw new IllegalArgumentException("File path must be absolute");
} }
try { try {
CSVFormat format = CSVFormat.DEFAULT.builder() CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get();
.setHeader()
.setSkipHeaderRecord(true)
.get();
this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format);
validateHeader(parser.getHeaderMap()); validateHeader(parser.getHeaderMap());
this.records = parser.iterator(); this.records = parser.iterator();
@@ -36,10 +35,12 @@ import ru.project.student.StudentBuilder;
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
} }
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return records.hasNext(); return records.hasNext();
} }
@Override @Override
public Student next() { public Student next() {
if (!records.hasNext()) { if (!records.hasNext()) {
@@ -60,13 +61,23 @@ import ru.project.student.StudentBuilder;
averageGrade = Double.parseDouble(averageGradeStr); averageGrade = Double.parseDouble(averageGradeStr);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Problem at line " + lineOfFile + ": cannot parse averageGrade '" + averageGradeStr + "' as double", e); "Problem at line "
+ lineOfFile
+ ": cannot parse averageGrade '"
+ averageGradeStr
+ "' as double",
e);
} }
try { try {
recordBookNumber = Integer.parseInt(recordBookNumberStr); recordBookNumber = Integer.parseInt(recordBookNumberStr);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Problem at line " + lineOfFile + ": cannot parse recordBookNumber '" + recordBookNumberStr + "' as int", e); "Problem at line "
+ lineOfFile
+ ": cannot parse recordBookNumber '"
+ recordBookNumberStr
+ "' as int",
e);
} }
return new StudentBuilder() return new StudentBuilder()
.setGroupNumber(groupNumber) .setGroupNumber(groupNumber)
@@ -74,10 +85,12 @@ import ru.project.student.StudentBuilder;
.setRecordBookNumber(recordBookNumber) .setRecordBookNumber(recordBookNumber)
.build(); .build();
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
parser.close(); parser.close();
} }
private static void validateHeader(Map<String, Integer> headerMap) { private static void validateHeader(Map<String, Integer> headerMap) {
String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER};
if (headerMap.size() != expected.length if (headerMap.size() != expected.length
@@ -85,4 +98,4 @@ import ru.project.student.StudentBuilder;
throw new IllegalArgumentException("Invalid CSV header"); throw new IllegalArgumentException("Invalid CSV header");
} }
} }
} }
@@ -1,8 +1,8 @@
package ru.project.search; package ru.project.search;
import ru.project.student.Student;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import ru.project.student.Student;
public class CompositeStudentSearchCriteria implements StudentSearchCriteria { public class CompositeStudentSearchCriteria implements StudentSearchCriteria {
private final SearchOperation operation; private final SearchOperation operation;
@@ -1,4 +1,7 @@
package ru.project.io; package ru.project.io;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
@@ -8,29 +11,24 @@ import java.util.Spliterators;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import ru.project.student.Student;
import ru.project.collection.MyList; import ru.project.collection.MyList;
import ru.project.collectors.MyCollectors; import ru.project.collectors.MyCollectors;
import static org.junit.jupiter.api.Assertions.*; import ru.project.student.Student;
class StudentCsvReaderTest { class StudentCsvReaderTest {
@TempDir @TempDir Path tempDir;
Path tempDir;
@Test @Test
void shouldTestreadValidCsvIntoMyList() throws IOException { void shouldTestreadValidCsvIntoMyList() throws IOException {
Path csv = tempDir.resolve("students.csv"); Path csv = tempDir.resolve("students.csv");
String content = String.join("\n", String content =
"groupNumber,averageGrade,recordBookNumber", String.join(
"M10,4.5,12345", "\n", "groupNumber,averageGrade,recordBookNumber", "M10,4.5,12345", "B05,3.8,12346");
"B05,3.8,12346"
);
Files.writeString(csv, content, StandardCharsets.UTF_8); Files.writeString(csv, content, StandardCharsets.UTF_8);
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
var studentStream = StreamSupport.stream( var studentStream =
Spliterators.spliteratorUnknownSize(reader, 0), StreamSupport.stream(Spliterators.spliteratorUnknownSize(reader, 0), false);
false MyList<Student> students = studentStream.collect(MyCollectors.toMyList());
);
MyList<Student> students = studentStream
.collect(MyCollectors.toMyList());
assertEquals(2, students.size()); assertEquals(2, students.size());
assertEquals("M10", students.get(0).getGroupNumber()); assertEquals("M10", students.get(0).getGroupNumber());
assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); assertEquals(4.5, students.get(0).getAverageGrade(), 0.0);
@@ -40,24 +38,27 @@ class StudentCsvReaderTest {
assertEquals(12346, students.get(1).getRecordBookNumber()); assertEquals(12346, students.get(1).getRecordBookNumber());
} }
} }
@Test @Test
void shouldTestinvalidHeaderThrowsException() throws IOException { void shouldTestinvalidHeaderThrowsException() throws IOException {
Path csv = tempDir.resolve("bad-header.csv"); Path csv = tempDir.resolve("bad-header.csv");
String content = """ String content =
"""
badGroup,badGrade,badBook badGroup,badGrade,badBook
M101,4.5,12345 M101,4.5,12345
"""; """;
Files.writeString(csv, content, StandardCharsets.UTF_8); Files.writeString(csv, content, StandardCharsets.UTF_8);
IllegalArgumentException ex = assertThrows( IllegalArgumentException ex =
IllegalArgumentException.class, assertThrows(
() -> new StudentCsvReader(csv.toAbsolutePath()) IllegalArgumentException.class, () -> new StudentCsvReader(csv.toAbsolutePath()));
);
assertTrue(ex.getMessage().contains("Invalid CSV header")); assertTrue(ex.getMessage().contains("Invalid CSV header"));
} }
@Test @Test
void shouldTestwrongColumnCountThrowsException() throws IOException { void shouldTestwrongColumnCountThrowsException() throws IOException {
Path csv = tempDir.resolve("wrong-columns.csv"); Path csv = tempDir.resolve("wrong-columns.csv");
String content = """ String content =
"""
groupNumber,averageGrade,recordBookNumber groupNumber,averageGrade,recordBookNumber
M101,4.5 M101,4.5
"""; """;
@@ -65,17 +66,16 @@ class StudentCsvReaderTest {
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
assertTrue(reader.hasNext()); assertTrue(reader.hasNext());
IllegalArgumentException ex = assertThrows( IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next);
IllegalArgumentException.class,
reader::next
);
assertTrue(ex.getMessage().contains("Expected 3 columns")); assertTrue(ex.getMessage().contains("Expected 3 columns"));
} }
} }
@Test @Test
void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException {
Path csv = tempDir.resolve("parse-error.csv"); Path csv = tempDir.resolve("parse-error.csv");
String content = """ String content =
"""
groupNumber,averageGrade,recordBookNumber groupNumber,averageGrade,recordBookNumber
M101,not-a-number,12345 M101,not-a-number,12345
"""; """;
@@ -83,17 +83,16 @@ class StudentCsvReaderTest {
try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { try (var reader = new StudentCsvReader(csv.toAbsolutePath())) {
assertTrue(reader.hasNext()); assertTrue(reader.hasNext());
IllegalArgumentException ex = assertThrows( IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next);
IllegalArgumentException.class,
reader::next
);
assertTrue(ex.getMessage().contains("cannot parse averageGrade")); assertTrue(ex.getMessage().contains("cannot parse averageGrade"));
} }
} }
@Test @Test
void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException {
Path csv = tempDir.resolve("empty-data.csv"); Path csv = tempDir.resolve("empty-data.csv");
String content = """ String content =
"""
groupNumber,averageGrade,recordBookNumber groupNumber,averageGrade,recordBookNumber
"""; """;
Files.writeString(csv, content, StandardCharsets.UTF_8); Files.writeString(csv, content, StandardCharsets.UTF_8);
@@ -105,14 +104,13 @@ class StudentCsvReaderTest {
assertThrows(NoSuchElementException.class, reader::next); assertThrows(NoSuchElementException.class, reader::next);
} }
} }
@Test @Test
void shouldTestrelativePathThrowsException() { void shouldTestrelativePathThrowsException() {
Path relative = Path.of("relative-path.csv"); Path relative = Path.of("relative-path.csv");
IllegalArgumentException ex = assertThrows( IllegalArgumentException ex =
IllegalArgumentException.class, assertThrows(IllegalArgumentException.class, () -> new StudentCsvReader(relative));
() -> new StudentCsvReader(relative)
);
assertTrue(ex.getMessage().contains("absolute")); assertTrue(ex.getMessage().contains("absolute"));
} }
} }