From 1ec1d68a02ae7acfbe14292ba3bec45470f04537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=BE=D0=BB=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=93=D0=B5?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D0=BD?= Date: Wed, 2 Sep 2026 13:14:28 +0400 Subject: [PATCH] style(code): unify formatting across io and search components --- .../java/ru/project/io/StudentCsvReader.java | 143 +++++++------ .../search/AverageGradeRangeCriteria.java | 28 +-- .../CompositeStudentSearchCriteria.java | 78 +++---- .../search/GroupNumberRangeCriteria.java | 34 ++-- .../search/RecordBookNumberRangeCriteria.java | 28 +-- .../ru/project/search/SearchOperation.java | 6 +- .../project/search/StudentSearchCriteria.java | 4 +- .../ru/project/io/StudentCsvReaderTest.java | 190 +++++++++--------- 8 files changed, 261 insertions(+), 250 deletions(-) diff --git a/app/src/main/java/ru/project/io/StudentCsvReader.java b/app/src/main/java/ru/project/io/StudentCsvReader.java index db2a4a9..7cc6deb 100644 --- a/app/src/main/java/ru/project/io/StudentCsvReader.java +++ b/app/src/main/java/ru/project/io/StudentCsvReader.java @@ -13,76 +13,89 @@ import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; import ru.project.student.Student; import ru.project.student.StudentBuilder; - public class StudentCsvReader implements Iterator, AutoCloseable { + +public class StudentCsvReader implements Iterator, AutoCloseable { private static final String GROUP_NUMBER = "groupNumber"; private static final String AVERAGE_GRADE = "averageGrade"; private static final String RECORD_BOOK_NUMBER = "recordBookNumber"; private final CSVParser parser; private final Iterator records; - private int lineOfFile=0; - public StudentCsvReader(Path path) { - if (!path.isAbsolute()) { - throw new IllegalArgumentException("File path must be absolute"); - } - try { - CSVFormat format = CSVFormat.DEFAULT.builder() - .setHeader() - .setSkipHeaderRecord(true) - .get(); - this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); - validateHeader(parser.getHeaderMap()); - this.records = parser.iterator(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + private int lineOfFile = 0; + + public StudentCsvReader(Path path) { + if (!path.isAbsolute()) { + throw new IllegalArgumentException("File path must be absolute"); } - @Override - public boolean hasNext() { - return records.hasNext(); - } - @Override - public Student next() { - if (!records.hasNext()) { - throw new NoSuchElementException("No more records at line " + (lineOfFile + 1)); - } - lineOfFile++; - CSVRecord record = records.next(); - if (record.size() != 3) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size()); - } - String groupNumber = record.get(GROUP_NUMBER); - String averageGradeStr = record.get(AVERAGE_GRADE); - String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER); - double averageGrade; - int recordBookNumber; - try { - averageGrade = Double.parseDouble(averageGradeStr); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ": cannot parse averageGrade '" + averageGradeStr + "' as double", e); - } - try { - recordBookNumber = Integer.parseInt(recordBookNumberStr); - } catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Problem at line " + lineOfFile + ": cannot parse recordBookNumber '" + recordBookNumberStr + "' as int", e); - } - return new StudentBuilder() - .setGroupNumber(groupNumber) - .setAverageGrade(averageGrade) - .setRecordBookNumber(recordBookNumber) - .build(); - } - @Override - public void close() throws IOException { - parser.close(); - } - private static void validateHeader(Map headerMap) { - String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; - if (headerMap.size() != expected.length - || !headerMap.keySet().containsAll(Arrays.asList(expected))) { - throw new IllegalArgumentException("Invalid CSV header"); - } + try { + CSVFormat format = CSVFormat.DEFAULT.builder().setHeader().setSkipHeaderRecord(true).get(); + this.parser = CSVParser.parse(path, StandardCharsets.UTF_8, format); + validateHeader(parser.getHeaderMap()); + this.records = parser.iterator(); + } catch (IOException e) { + throw new UncheckedIOException(e); } } + + @Override + public boolean hasNext() { + return records.hasNext(); + } + + @Override + public Student next() { + if (!records.hasNext()) { + throw new NoSuchElementException("No more records at line " + (lineOfFile + 1)); + } + lineOfFile++; + CSVRecord record = records.next(); + if (record.size() != 3) { + throw new IllegalArgumentException( + "Problem at line " + lineOfFile + ". Expected 3 columns, but got " + record.size()); + } + String groupNumber = record.get(GROUP_NUMBER); + String averageGradeStr = record.get(AVERAGE_GRADE); + String recordBookNumberStr = record.get(RECORD_BOOK_NUMBER); + double averageGrade; + int recordBookNumber; + try { + averageGrade = Double.parseDouble(averageGradeStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + + lineOfFile + + ": cannot parse averageGrade '" + + averageGradeStr + + "' as double", + e); + } + try { + recordBookNumber = Integer.parseInt(recordBookNumberStr); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Problem at line " + + lineOfFile + + ": cannot parse recordBookNumber '" + + recordBookNumberStr + + "' as int", + e); + } + return new StudentBuilder() + .setGroupNumber(groupNumber) + .setAverageGrade(averageGrade) + .setRecordBookNumber(recordBookNumber) + .build(); + } + + @Override + public void close() throws IOException { + parser.close(); + } + + private static void validateHeader(Map headerMap) { + String[] expected = {GROUP_NUMBER, AVERAGE_GRADE, RECORD_BOOK_NUMBER}; + if (headerMap.size() != expected.length + || !headerMap.keySet().containsAll(Arrays.asList(expected))) { + throw new IllegalArgumentException("Invalid CSV header"); + } + } +} diff --git a/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java index a53a7c5..dd7ea4b 100644 --- a/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java +++ b/app/src/main/java/ru/project/search/AverageGradeRangeCriteria.java @@ -3,20 +3,20 @@ package ru.project.search; import ru.project.student.Student; public class AverageGradeRangeCriteria implements StudentSearchCriteria { - private final double min; - private final double max; + private final double min; + private final double max; - public AverageGradeRangeCriteria(double min, double max) { - if (min > max) { - throw new IllegalArgumentException("Min grade must be <= max grade"); - } - this.min = min; - this.max = max; + public AverageGradeRangeCriteria(double min, double max) { + if (min > max) { + throw new IllegalArgumentException("Min grade must be <= max grade"); } + this.min = min; + this.max = max; + } - @Override - public boolean matches(Student student) { - double grade = student.getAverageGrade(); - return grade >= min && grade <= max; - } -} \ No newline at end of file + @Override + public boolean matches(Student student) { + double grade = student.getAverageGrade(); + return grade >= min && grade <= max; + } +} diff --git a/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java index 063d087..3b87850 100644 --- a/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java +++ b/app/src/main/java/ru/project/search/CompositeStudentSearchCriteria.java @@ -1,51 +1,51 @@ package ru.project.search; -import ru.project.student.Student; import java.util.ArrayList; import java.util.List; +import ru.project.student.Student; public class CompositeStudentSearchCriteria implements StudentSearchCriteria { - private final SearchOperation operation; - private final List criteriaList = new ArrayList<>(); + private final SearchOperation operation; + private final List criteriaList = new ArrayList<>(); - public CompositeStudentSearchCriteria(SearchOperation operation) { - if (operation == null) { - throw new IllegalArgumentException("Operation must not be null"); - } - this.operation = operation; + public CompositeStudentSearchCriteria(SearchOperation operation) { + if (operation == null) { + throw new IllegalArgumentException("Operation must not be null"); + } + this.operation = operation; + } + + public void add(StudentSearchCriteria criteria) { + if (criteria == null) { + throw new IllegalArgumentException("Criteria must not be null"); + } + criteriaList.add(criteria); + } + + public void remove(StudentSearchCriteria criteria) { + criteriaList.remove(criteria); + } + + @Override + public boolean matches(Student student) { + if (criteriaList.isEmpty()) { + return operation == SearchOperation.INTERSECTION; } - public void add(StudentSearchCriteria criteria) { - if (criteria == null) { - throw new IllegalArgumentException("Criteria must not be null"); + if (operation == SearchOperation.UNION) { + for (StudentSearchCriteria c : criteriaList) { + if (c.matches(student)) { + return true; } - criteriaList.add(criteria); - } - - public void remove(StudentSearchCriteria criteria) { - criteriaList.remove(criteria); - } - - @Override - public boolean matches(Student student) { - if (criteriaList.isEmpty()) { - return operation == SearchOperation.INTERSECTION; - } - - if (operation == SearchOperation.UNION) { - for (StudentSearchCriteria c : criteriaList) { - if (c.matches(student)) { - return true; - } - } - return false; - } else { - for (StudentSearchCriteria c : criteriaList) { - if (!c.matches(student)) { - return false; - } - } - return true; + } + return false; + } else { + for (StudentSearchCriteria c : criteriaList) { + if (!c.matches(student)) { + return false; } + } + return true; } -} \ No newline at end of file + } +} diff --git a/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java index 61ea406..636b64d 100644 --- a/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java +++ b/app/src/main/java/ru/project/search/GroupNumberRangeCriteria.java @@ -3,23 +3,23 @@ package ru.project.search; import ru.project.student.Student; public class GroupNumberRangeCriteria implements StudentSearchCriteria { - private final String min; - private final String max; + private final String min; + private final String max; - public GroupNumberRangeCriteria(String min, String max) { - if (min == null || max == null) { - throw new IllegalArgumentException("Min and max must not be null"); - } - if (min.compareTo(max) > 0) { - throw new IllegalArgumentException("Min group must be <= max group"); - } - this.min = min; - this.max = max; + public GroupNumberRangeCriteria(String min, String max) { + if (min == null || max == null) { + throw new IllegalArgumentException("Min and max must not be null"); } + if (min.compareTo(max) > 0) { + throw new IllegalArgumentException("Min group must be <= max group"); + } + this.min = min; + this.max = max; + } - @Override - public boolean matches(Student student) { - String group = student.getGroupNumber(); - return group.compareTo(min) >= 0 && group.compareTo(max) <= 0; - } -} \ No newline at end of file + @Override + public boolean matches(Student student) { + String group = student.getGroupNumber(); + return group.compareTo(min) >= 0 && group.compareTo(max) <= 0; + } +} diff --git a/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java index 41131b4..39998f7 100644 --- a/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java +++ b/app/src/main/java/ru/project/search/RecordBookNumberRangeCriteria.java @@ -3,20 +3,20 @@ package ru.project.search; import ru.project.student.Student; public class RecordBookNumberRangeCriteria implements StudentSearchCriteria { - private final int min; - private final int max; + private final int min; + private final int max; - public RecordBookNumberRangeCriteria(int min, int max) { - if (min > max) { - throw new IllegalArgumentException("Min record number must be <= max"); - } - this.min = min; - this.max = max; + public RecordBookNumberRangeCriteria(int min, int max) { + if (min > max) { + throw new IllegalArgumentException("Min record number must be <= max"); } + this.min = min; + this.max = max; + } - @Override - public boolean matches(Student student) { - int number = student.getRecordBookNumber(); - return number >= min && number <= max; - } -} \ No newline at end of file + @Override + public boolean matches(Student student) { + int number = student.getRecordBookNumber(); + return number >= min && number <= max; + } +} diff --git a/app/src/main/java/ru/project/search/SearchOperation.java b/app/src/main/java/ru/project/search/SearchOperation.java index d616f97..badfc12 100644 --- a/app/src/main/java/ru/project/search/SearchOperation.java +++ b/app/src/main/java/ru/project/search/SearchOperation.java @@ -1,6 +1,6 @@ package ru.project.search; public enum SearchOperation { - UNION, - INTERSECTION -} \ No newline at end of file + UNION, + INTERSECTION +} diff --git a/app/src/main/java/ru/project/search/StudentSearchCriteria.java b/app/src/main/java/ru/project/search/StudentSearchCriteria.java index f157982..283dc65 100644 --- a/app/src/main/java/ru/project/search/StudentSearchCriteria.java +++ b/app/src/main/java/ru/project/search/StudentSearchCriteria.java @@ -3,5 +3,5 @@ package ru.project.search; import ru.project.student.Student; public interface StudentSearchCriteria { - boolean matches(Student student); -} \ No newline at end of file + boolean matches(Student student); +} diff --git a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java index 3541f35..2cc4c35 100644 --- a/app/src/test/java/ru/project/io/StudentCsvReaderTest.java +++ b/app/src/test/java/ru/project/io/StudentCsvReaderTest.java @@ -1,4 +1,7 @@ package ru.project.io; + +import static org.junit.jupiter.api.Assertions.*; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -8,111 +11,106 @@ import java.util.Spliterators; import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import ru.project.student.Student; import ru.project.collection.MyList; import ru.project.collectors.MyCollectors; -import static org.junit.jupiter.api.Assertions.*; +import ru.project.student.Student; + class StudentCsvReaderTest { - @TempDir - Path tempDir; - @Test - void shouldTestreadValidCsvIntoMyList() throws IOException { - Path csv = tempDir.resolve("students.csv"); - String content = String.join("\n", - "groupNumber,averageGrade,recordBookNumber", - "M10,4.5,12345", - "B05,3.8,12346" - ); - Files.writeString(csv, content, StandardCharsets.UTF_8); - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - var studentStream = StreamSupport.stream( - Spliterators.spliteratorUnknownSize(reader, 0), - false - ); - MyList students = studentStream - .collect(MyCollectors.toMyList()); - assertEquals(2, students.size()); - assertEquals("M10", students.get(0).getGroupNumber()); - assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); - assertEquals(12345, students.get(0).getRecordBookNumber()); - assertEquals("B05", students.get(1).getGroupNumber()); - assertEquals(3.8, students.get(1).getAverageGrade(), 0.0); - assertEquals(12346, students.get(1).getRecordBookNumber()); - } - } - @Test - void shouldTestinvalidHeaderThrowsException() throws IOException { - Path csv = tempDir.resolve("bad-header.csv"); - String content = """ - badGroup,badGrade,badBook - M101,4.5,12345 - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - () -> new StudentCsvReader(csv.toAbsolutePath()) - ); - assertTrue(ex.getMessage().contains("Invalid CSV header")); - } - @Test - void shouldTestwrongColumnCountThrowsException() throws IOException { - Path csv = tempDir.resolve("wrong-columns.csv"); - String content = """ - groupNumber,averageGrade,recordBookNumber - M101,4.5 - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - assertTrue(reader.hasNext()); + @TempDir Path tempDir; - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - reader::next - ); - assertTrue(ex.getMessage().contains("Expected 3 columns")); - } + @Test + void shouldTestreadValidCsvIntoMyList() throws IOException { + Path csv = tempDir.resolve("students.csv"); + String content = + String.join( + "\n", "groupNumber,averageGrade,recordBookNumber", "M10,4.5,12345", "B05,3.8,12346"); + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + var studentStream = + StreamSupport.stream(Spliterators.spliteratorUnknownSize(reader, 0), false); + MyList students = studentStream.collect(MyCollectors.toMyList()); + assertEquals(2, students.size()); + assertEquals("M10", students.get(0).getGroupNumber()); + assertEquals(4.5, students.get(0).getAverageGrade(), 0.0); + assertEquals(12345, students.get(0).getRecordBookNumber()); + assertEquals("B05", students.get(1).getGroupNumber()); + assertEquals(3.8, students.get(1).getAverageGrade(), 0.0); + assertEquals(12346, students.get(1).getRecordBookNumber()); } - @Test - void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { - Path csv = tempDir.resolve("parse-error.csv"); - String content = """ - groupNumber,averageGrade,recordBookNumber - M101,not-a-number,12345 - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - assertTrue(reader.hasNext()); + } - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - reader::next - ); - assertTrue(ex.getMessage().contains("cannot parse averageGrade")); - } + @Test + void shouldTestinvalidHeaderThrowsException() throws IOException { + Path csv = tempDir.resolve("bad-header.csv"); + String content = + """ + badGroup,badGrade,badBook + M101,4.5,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + IllegalArgumentException ex = + assertThrows( + IllegalArgumentException.class, () -> new StudentCsvReader(csv.toAbsolutePath())); + assertTrue(ex.getMessage().contains("Invalid CSV header")); + } + + @Test + void shouldTestwrongColumnCountThrowsException() throws IOException { + Path csv = tempDir.resolve("wrong-columns.csv"); + String content = + """ + groupNumber,averageGrade,recordBookNumber + M101,4.5 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertTrue(reader.hasNext()); + + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next); + assertTrue(ex.getMessage().contains("Expected 3 columns")); } - @Test - void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { - Path csv = tempDir.resolve("empty-data.csv"); - String content = """ - groupNumber,averageGrade,recordBookNumber - """; - Files.writeString(csv, content, StandardCharsets.UTF_8); + } - // ИСПРАВЛЕНО: добавлен try-with-resources - try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { - assertFalse(reader.hasNext()); + @Test + void shouldTestparseErrorThrowsExceptionWithDetails() throws IOException { + Path csv = tempDir.resolve("parse-error.csv"); + String content = + """ + groupNumber,averageGrade,recordBookNumber + M101,not-a-number,12345 + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertTrue(reader.hasNext()); - assertThrows(NoSuchElementException.class, reader::next); - } + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, reader::next); + assertTrue(ex.getMessage().contains("cannot parse averageGrade")); } - @Test - void shouldTestrelativePathThrowsException() { - Path relative = Path.of("relative-path.csv"); + } - IllegalArgumentException ex = assertThrows( - IllegalArgumentException.class, - () -> new StudentCsvReader(relative) - ); - assertTrue(ex.getMessage().contains("absolute")); + @Test + void shouldTestemptyDataAfterHeaderThrowsNoSuchElement() throws IOException { + Path csv = tempDir.resolve("empty-data.csv"); + String content = + """ + groupNumber,averageGrade,recordBookNumber + """; + Files.writeString(csv, content, StandardCharsets.UTF_8); + + // ИСПРАВЛЕНО: добавлен try-with-resources + try (var reader = new StudentCsvReader(csv.toAbsolutePath())) { + assertFalse(reader.hasNext()); + + assertThrows(NoSuchElementException.class, reader::next); } - } \ No newline at end of file + } + + @Test + void shouldTestrelativePathThrowsException() { + Path relative = Path.of("relative-path.csv"); + + IllegalArgumentException ex = + assertThrows(IllegalArgumentException.class, () -> new StudentCsvReader(relative)); + assertTrue(ex.getMessage().contains("absolute")); + } +}