uh oh first commit

This commit is contained in:
2026-03-07 10:31:30 +03:00
commit adf21aad25
64 changed files with 2049 additions and 0 deletions
+181
View File
@@ -0,0 +1,181 @@
package file
import models.Car
import models.Coordinates
import models.HumanBeing
import models.WeaponType
import java.io.File
import java.io.FileNotFoundException
import java.io.OutputStreamWriter
import java.time.LocalDate
import java.time.format.DateTimeParseException
import java.util.Scanner
import java.util.UUID
/**
* Менеджер файлового хранилища коллекции.
*
* Читает данные с помощью [Scanner], записывает через [OutputStreamWriter].
* Формат хранения — CSV, одна строка = один объект [HumanBeing].
*
* Порядок полей в CSV:
* `id,name,coordX,coordY,creationDate,realHero,hasToothpick,
* impactSpeed,soundtrackName,minutesOfWaiting,weaponType,carCool`
*
* @property filePath путь к CSV-файлу, полученный из аргумента командной строки
*/
class FileManager(private val filePath: String) {
companion object {
private const val DELIMITER = ","
private const val HEADER =
"id,name,coordX,coordY,creationDate,realHero," +
"hasToothpick,impactSpeed,soundtrackName,minutesOfWaiting,weaponType,carCool"
}
/**
* Читает коллекцию из CSV-файла с помощью [Scanner].
*
* Пропускает заголовок и битые строки (с предупреждением в stderr).
* При отсутствии файла или проблемах с доступом возвращает пустой список.
*
* @return список корректно распарсенных объектов [HumanBeing]
*/
fun read(): List<HumanBeing> {
val file = File(filePath)
if (!file.exists()) {
System.err.println("Файл '$filePath' не найден. Коллекция будет пустой.")
return emptyList()
}
if (!file.canRead()) {
System.err.println("Нет прав на чтение файла '$filePath'. Коллекция будет пустой.")
return emptyList()
}
val result = mutableListOf<HumanBeing>()
var lineNumber = 0
try {
Scanner(file, Charsets.UTF_8).use { scanner ->
while (scanner.hasNextLine()) {
val line = scanner.nextLine().trim()
lineNumber++
// Пропускаем заголовок и пустые строки
if (lineNumber == 1 && line.startsWith("id")) continue
if (line.isBlank()) continue
parseLine(line, lineNumber)
?.let { result.add(it) }
?: System.err.println("Строка $lineNumber пропущена: некорректные данные → '$line'")
}
}
} catch (e: FileNotFoundException) {
System.err.println("Файл не найден во время чтения: ${e.message}")
}
println("Загружено элементов: ${result.size} из файла '$filePath'")
return result
}
/**
* Парсит одну CSV-строку в объект [HumanBeing].
*
* @param line строка CSV
* @param lineNumber номер строки (для сообщений об ошибках)
* @return объект [HumanBeing] или null при ошибке парсинга
*/
private fun parseLine(line: String, lineNumber: Int): HumanBeing? {
return try {
val parts = line.split(DELIMITER, limit = 12)
if (parts.size < 12) {
System.err.println("Строка $lineNumber: ожидалось 12 полей, найдено ${parts.size}")
return null
}
val id = UUID.fromString(parts[0].trim())
val name = parts[1].trim()
val coordX = parts[2].trim().toLong()
val coordY = parts[3].trim().toInt()
val creationDate = LocalDate.parse(parts[4].trim())
val realHero = parts[5].trim().toBoolean()
val hasToothpick = parts[6].trim().toBoolean()
val impactSpeed = parts[7].trim().toDouble()
val soundtrackName = parts[8].trim()
val minutesOfWaiting = parts[9].trim().toFloat()
val weaponType = parts[10].trim().let {
if (it.equals("null", ignoreCase = true) || it.isBlank()) null
else WeaponType.valueOf(it)
}
val carCool = parts[11].trim().toBoolean()
// самая лучшая валидация (100%)
require(name.isNotBlank()) { "name не может быть пустым" }
// от такого может быть диабет
HumanBeing(
id = id, name = name, coordinates = Coordinates(coordX, coordY),
creationDate = creationDate, realHero = realHero, hasToothpick = hasToothpick,
impactSpeed = impactSpeed, soundtrackName = soundtrackName,
minutesOfWaiting = minutesOfWaiting, weaponType = weaponType, car = Car(carCool)
)
} catch (e: IllegalArgumentException) {
System.err.println("Строка $lineNumber — ошибка данных: ${e.message}")
null
} catch (e: DateTimeParseException) {
System.err.println("Строка $lineNumber — неверный формат даты: ${e.message}")
null
} catch (e: Exception) {
System.err.println("Строка $lineNumber — неожиданная ошибка: ${e.message}")
null
}
}
/**
* Записывает коллекцию в CSV-файл с помощью [OutputStreamWriter].
*
* @param items коллекция объектов [HumanBeing] для записи
* @return true если запись прошла успешно
*/
fun write(items: Collection<HumanBeing>): Boolean {
val file = File(filePath)
if (file.exists() && !file.canWrite()) {
System.err.println("Нет прав на запись в файл '$filePath'.")
return false
}
return try {
OutputStreamWriter(file.outputStream(), Charsets.UTF_8).use { writer ->
writer.write(HEADER)
writer.write("\n")
items.forEach { h ->
writer.write(toCSVLine(h))
writer.write("\n")
}
}
println("Коллекция сохранена в '$filePath' (${items.size} элементов).")
true
} catch (e: SecurityException) {
System.err.println("Отказано в доступе к файлу '$filePath': ${e.message}")
false
} catch (e: Exception) {
System.err.println("Ошибка записи в файл: ${e.message}")
false
}
}
/**
* Сериализует объект [HumanBeing] в строку CSV.
*
* @param h объект для сериализации
* @return строка в формате CSV
*/
private fun toCSVLine(h: HumanBeing): String = listOf(
h.id, h.name, h.coordinates.x, h.coordinates.y, h.creationDate,
h.realHero, h.hasToothpick, h.impactSpeed, h.soundtrackName,
h.minutesOfWaiting, h.weaponType ?: "null", h.car.cool
).joinToString(DELIMITER)
}