initial commit
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
plugins {
|
||||
id 'org.jetbrains.kotlin.jvm' version '2.2.0'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0'
|
||||
id 'org.jetbrains.dokka' version '2.2.0'
|
||||
}
|
||||
|
||||
group = 'lab6.prog'
|
||||
version = '1.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain(17)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package commands
|
||||
|
||||
import models.HumanBeing
|
||||
import network.Response
|
||||
|
||||
interface Command {
|
||||
val name: String
|
||||
val description: String
|
||||
val args: List<CommandArgType>
|
||||
val type: CommandType
|
||||
|
||||
fun execute(args: List<String>, humanBeing: HumanBeing? = null): Response
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package commands
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
// why there's only two types? yes.
|
||||
@Serializable
|
||||
enum class CommandArgType {
|
||||
STRING,
|
||||
NONE
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package commands
|
||||
|
||||
enum class CommandType {
|
||||
SIMPLE,
|
||||
MODEL
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Автомобиль, которым владеет [HumanBeing].
|
||||
*
|
||||
* @property cool признак того, что автомобиль крутой
|
||||
*/
|
||||
@Serializable
|
||||
data class Car(
|
||||
val cool: Boolean
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Координаты местоположения [HumanBeing].
|
||||
*
|
||||
* @property x координата X
|
||||
* @property y координата Y
|
||||
*/
|
||||
|
||||
@Serializable
|
||||
data class Coordinates(
|
||||
val x: Long,
|
||||
val y: Int
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
package models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import network.utils.LocalDateSerializer
|
||||
import network.utils.UUIDSerializer
|
||||
import java.time.LocalDate
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Основная сущность коллекции — Райан Гослинг.
|
||||
*
|
||||
* @property id уникальный идентификатор, генерируется автоматически
|
||||
* @property name имя героя (Райан Гослинг), не может быть null или пустой строкой
|
||||
* @property coordinates координаты героя, не может быть null
|
||||
* @property creationDate дата создания записи, генерируется автоматически
|
||||
* @property realHero является ли настоящим героем (конечно)
|
||||
* @property hasToothpick есть ли зубочистка (естественно)
|
||||
* @property impactSpeed скорость удара, не может быть null
|
||||
* @property soundtrackName название саундтрека, не может быть null (Nightcall - Kavinsky)
|
||||
* @property minutesOfWaiting количество минут ожидания
|
||||
* @property weaponType тип оружия, может быть null
|
||||
* @property car автомобиль героя, не может быть null
|
||||
*/
|
||||
@Serializable
|
||||
data class HumanBeing(
|
||||
@Serializable(with = UUIDSerializer::class)
|
||||
val id: UUID = UUID.randomUUID(),
|
||||
val name: String,
|
||||
val coordinates: Coordinates,
|
||||
@Serializable(with = LocalDateSerializer::class)
|
||||
val creationDate: LocalDate = LocalDate.now(),
|
||||
val realHero: Boolean,
|
||||
val hasToothpick: Boolean,
|
||||
val impactSpeed: Double,
|
||||
val soundtrackName: String,
|
||||
val minutesOfWaiting: Float,
|
||||
val weaponType: WeaponType?,
|
||||
val car: Car
|
||||
) : Comparable<HumanBeing> {
|
||||
|
||||
init {
|
||||
require(name.isNotBlank()) { "Имя не может быть пустым" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Сравниваем по имени и id
|
||||
*/
|
||||
override fun compareTo(other: HumanBeing): Int {
|
||||
val byName = this.name.compareTo(other.name)
|
||||
if (byName != 0) return byName
|
||||
return this.id.toString().compareTo(other.id.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Два объекта считаются одинаковыми, если совпадают их [id].
|
||||
*/
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is HumanBeing) return false
|
||||
return id == other.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
|
||||
/**
|
||||
* Красивое строковое представление для команды [show].
|
||||
*/
|
||||
override fun toString(): String =
|
||||
"""
|
||||
|HumanBeing (It Drives) {
|
||||
| id = $id
|
||||
| name = $name (a.k.a. Ryan Gosling)
|
||||
| coordinates = (x=${coordinates.x}, y=${coordinates.y})
|
||||
| creationDate = $creationDate
|
||||
| realHero = $realHero
|
||||
| hasToothpick = $hasToothpick
|
||||
| impactSpeed = $impactSpeed
|
||||
| soundtrackName = $soundtrackName
|
||||
| minutesOfWait = $minutesOfWaiting
|
||||
| weaponType = ${weaponType ?: "null"}
|
||||
| car.cool = ${car.cool}
|
||||
|}
|
||||
""".trimMargin()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Перечисление типов оружия, которым может владеть [HumanBeing].
|
||||
* Поле [weaponType] в [HumanBeing] может быть null.
|
||||
*/
|
||||
@Serializable
|
||||
enum class WeaponType {
|
||||
HAMMER,
|
||||
SHOTGUN,
|
||||
BAT;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package network
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
/**
|
||||
* Общий экземпляр Json для всего приложения.
|
||||
* ignoreUnknownKeys — устойчивость к добавлению новых полей в протокол.
|
||||
*/
|
||||
val AppJson = Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package network
|
||||
|
||||
import commands.CommandArgType
|
||||
import commands.CommandType
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class CommandInfo(
|
||||
val name: String,
|
||||
val description: String,
|
||||
val args: List<CommandArgType>,
|
||||
val type: CommandType
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
package network
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import models.HumanBeing
|
||||
|
||||
/**
|
||||
* Запрос от клиента к серверу.
|
||||
*
|
||||
* @property commandName имя команды ("add", "show", "remove_by_id" ...)
|
||||
* @property args строковые аргументы (UUID для update/remove_by_id и т.п.)
|
||||
* @property humanBeing объект для команд: add, update, add_if_max, add_if_min
|
||||
*/
|
||||
@Serializable
|
||||
data class Request(
|
||||
val commandName: String,
|
||||
val args: List<String> = emptyList(),
|
||||
val humanBeing: HumanBeing? = null
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
package network
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Ответ сервера клиенту.
|
||||
*
|
||||
* @property success true — команда выполнена, false — ошибка
|
||||
* @property message текст для вывода пользователю
|
||||
*
|
||||
* -- только при синхронизации --
|
||||
* @property knownCommands - общий список известных команд
|
||||
* @property humanBeingCommands - команды, требующие ввода коллекции [models.HumanBeing]
|
||||
* @property commandsWArgs - команды, требующие ввода аргументов
|
||||
*/
|
||||
@Serializable
|
||||
data class Response(
|
||||
val success: Boolean,
|
||||
val message: String,
|
||||
val commands: List<CommandInfo>? = null,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
package network.utils
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.*
|
||||
import java.time.LocalDate
|
||||
|
||||
/**
|
||||
* Кастомный KSerializer для LocalDate.
|
||||
* Сериализует как ISO строку: "2025-04-12"
|
||||
*/
|
||||
object LocalDateSerializer : KSerializer<LocalDate> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: LocalDate) =
|
||||
encoder.encodeString(value.toString())
|
||||
|
||||
override fun deserialize(decoder: Decoder): LocalDate =
|
||||
LocalDate.parse(decoder.decodeString())
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package network.utils
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.*
|
||||
import kotlinx.serialization.encoding.*
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Кастомный KSerializer для UUID.
|
||||
* UUID не поддерживается kotlinx.serialization из коробки.
|
||||
*/
|
||||
object UUIDSerializer : KSerializer<UUID> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) =
|
||||
encoder.encodeString(value.toString())
|
||||
|
||||
override fun deserialize(decoder: Decoder): UUID =
|
||||
UUID.fromString(decoder.decodeString())
|
||||
}
|
||||
Reference in New Issue
Block a user