Merge commit '1d57fb83a4db05f5a6b4c997d4bbbfc74e8c7d04' as 'Programming/ProgLab6'

This commit is contained in:
LeterZP
2026-05-17 15:29:12 +03:00
62 changed files with 2722 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Kotlin ###
.kotlin
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
save.json
logs
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<compositeConfiguration>
<compositeBuild compositeDefinitionSource="IDE">
<builds>
<build path="$PROJECT_DIR$/client" name="client" />
</builds>
</compositeBuild>
</compositeConfiguration>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/client" />
<option value="$PROJECT_DIR$/general" />
<option value="$PROJECT_DIR$/server" />
</set>
</option>
</GradleProjectSettings>
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$/client" />
</GradleProjectSettings>
</option>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0" />
</component>
</project>
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="corretto-17 (2)" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+33
View File
@@ -0,0 +1,33 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '2.2.0'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0'
id 'com.gradleup.shadow' version '9.3.1' apply false
id 'org.jetbrains.dokka' version '2.2.0'
}
group = 'com.leterzp.prog'
version = '2.0'
repositories {
mavenCentral()
}
subprojects {
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.jetbrains.kotlin.plugin.serialization'
apply plugin: 'org.jetbrains.dokka'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
}
kotlin {
jvmToolchain(17)
}
}
+16
View File
@@ -0,0 +1,16 @@
group = 'com.leterzp.prog.client'
version = '2.0'
dependencies {
implementation project(":general")
}
apply plugin: 'com.gradleup.shadow'
shadowJar {
archiveBaseName.set('ProgLab6.client')
archiveVersion.set('2.0')
manifest {
attributes 'Main-Class': 'ClientKt'
}
}
@@ -0,0 +1,19 @@
import core.ConnectionManager
import core.InteractiveMode
import exceptions.ProgramExitException
import io.IOManager
import java.net.InetAddress
fun main() {
val host = InetAddress.getLocalHost()
val port = 8841
val io = IOManager()
val cm = ConnectionManager(io, host, port)
try {
val im = InteractiveMode(io, cm)
im.start()
} catch (e: ProgramExitException) {
io.write(e.message+"\n")
}
}
@@ -0,0 +1,41 @@
package commands
import core.CommandInvoker
/**
* Команда для исполнения скрипта.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class ExecuteScriptCommand(override val ci: CommandInvoker): Command(ci) {
override val argumentsAmount: Int = 1
override fun execute(arguments: List<String>) {
super.execute(arguments)
if (arguments[0] !in ci.executionHistory) {
ci.executionHistory.add(arguments[0])
val previousSource = ci.io.source
ci.io.source = arguments[0]
ci.addNext(ci.io.read())
ci.io.source = previousSource
} else {
result = "Обнаружена бесконечная рекурсия. Отказ в запуске скрипта ${arguments[0]}.\n"
}
}
override fun describe(): String {
return "Запускает команды из скрипта"
}
override fun getSyntax(): String {
return "[file_name]"
}
override fun getName(): String {
return "execute_script"
}
}
@@ -0,0 +1,73 @@
package commands
import core.CommandInvoker
import exceptions.CommandNotFoundException
import exceptions.InvalidAmountOfArgumentsException
/**
* Команда для вывода списка доступных команд.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class HelpCommand(override val ci: CommandInvoker): Command(ci) {
/**
* Выдаёт полную информацию о команде.
*
* @param command Команда типа [Command].
*
* @return Информацию о команде типа [String].
*
* @since 1.0
*/
private fun getFullInfo(command: CommandWrapper): String {
return " --" + command.string + " : " + command.describe
}
/**
* Выдаёт краткую информацию о команде.
*
* @param command Команда типа [Command].
*
* @return Информацию о команде типа [String].
*
* @since 1.0
*/
private fun getInfo(command: CommandWrapper): String {
return " --" + command.name + " : " + command.describe
}
override fun execute(arguments: List<String>) {
try {
if (arguments.isEmpty()) {
result = "Список доступных команд:" + "\n"
for (command in ci.commands.values) {
result += getInfo(command) + "\n"
}
} else if (arguments.size == 1) {
if (arguments[0] in ci.commands.keys) {
result += getFullInfo(ci.commands.get(arguments[0])!!) + "\n"
} else {
throw CommandNotFoundException(arguments[0])
}
} else throw InvalidAmountOfArgumentsException(this, arguments.size)
} catch (e: CommandNotFoundException) {
result = e.message + "\n"
}
}
override fun getName(): String {
return "help"
}
override fun getSyntax(): String {
return "[<null> | command]"
}
override fun describe(): String {
return "Выводит информацию о всех командах либо описание одной команды"
}
}
@@ -0,0 +1,208 @@
package core
import commands.*
import elements.CityBuilder
import exceptions.CommandNotFoundException
import exceptions.ConnectionException
import exceptions.InvalidElementValueException
import exceptions.NoNextCommandException
import exceptions.ProgramExitException
import io.IOManager
import java.util.Stack
/**
* Класс вызова команд.
*
* Позволяет вызывать инициализированные в нём команды для работы с коллекцией посредством [CollectionManager].
*
* @param io [IOManager], откуда читаются команды.
*
* @property commands [HashMap] команд, содержащий имя команды типа [String] и саму команду типа [Command].
*
* @constructor Принимает все описанные выше параметры, создавая объект, уже содержащий в себе стандартный набор команд.
*
* @since 1.0
*/
class CommandInvoker(val io: IOManager, private val cm: ConnectionManager): CommandInvokerInterface {
val commands: HashMap<String, CommandWrapper> = HashMap()
private val nextArgument: Stack<String> = Stack<String>()
val executionHistory = Stack<String>()
init {
try {
getCommands()
} catch (e: ConnectionException) {
io.write(e.message + "\n")
throw ProgramExitException()
}
}
/**
* Инициализирует команду, добавляя её в список возможных к использованию.
*
* @param command Команда для инициализации, типа [Command].
*
* @since 1.0
*/
fun initializeCommand(command: CommandWrapper) {
val name: String = command.name
commands[name] = command
}
/**
* Вызывает команду, читая её либо из очереди команд, либо из консоли в случае, если очередь пуста.
*
* @since 1.0
*/
override fun executeCommand(cw: CommandWrapper): CommandWrapper {
when (cw.name) {
"help" -> {
val help = HelpCommand(this)
help.execute(cw.arguments)
cw.result = help.result
return cw
}
"execute_script" -> {
val script = ExecuteScriptCommand(this)
script.execute(cw.arguments)
cw.result = script.result
return cw
}
"exit" -> {
val exit = ExitCommand(this)
exit.execute(cw.arguments)
cw.result = exit.result
return cw
}
else -> {
getCommands()
val result = cm.sendAndReceive(cw)
return result
}
}
}
fun readCommand() {
var instruction: List<String> = try {
readNext().trim().split(" ")
} catch (_: NoNextCommandException) {
io.read().trim().split(" ")
}
if (instruction.size == 1 && instruction[0] == "") return
try {
if (instruction[0] !in run {
val list1 = mutableListOf<String>()
for (i in this@CommandInvoker.getStandardCommands()) {
list1.add(i.name)
}
list1
}) {
// getCommands()
if (instruction[0] !in commands.keys) throw CommandNotFoundException(instruction[0])
}
val command = commands[instruction[0]]!!
instruction = validateCommand(command, instruction)
command.arguments = instruction.minus(instruction[0])
io.write(executeCommand(command).result)
} catch (e: CommandNotFoundException) {
io.write(e.message + "\n")
} catch (e: InvalidElementValueException) {
io.write(e.message + "\n")
}
}
/**
* Читает первую в очереди команду.
*
* @return Команду с аргументами типа [String].
*
* @throws NoNextCommandException В случае, если очередь из команд пуста.
*
* @since 1.0
*/
fun readNext(): String {
val result: String
if (nextArgument.isEmpty()) {
executionHistory.clear()
throw NoNextCommandException()
}
else {
result = nextArgument.pop()
}
return result
}
/**
* Добавляет одну или несколько команд в очередь.
*
* @param instructions Одна или несколько команд с аргументами типа [String].
*
* @since 1.0
*/
fun addNext(instructions: String) {
val values: List<String> = instructions.lines().reversed()
for (instruction in values) {
nextArgument.push(instruction)
}
}
/**
* Выбирает следующее значение для ввода.
*
* @see [IOManager.askForValue].
*/
fun nextValue(output: String): String {
val input: String
if (nextArgument.isEmpty()) {
input = io.askForValue(output)
} else {
input = nextArgument.pop()
}
return input
}
fun getCommands() {
val list = cm.askCommandList()
commands.clear()
for (command in list) {
initializeCommand(command)
}
for (st in getStandardCommands()) {
initializeCommand(st)
}
}
fun validateCommand(command: CommandWrapper, instruction: List<String>): List<String> {
if (command.argumentsAmount > 1) {
val args = mutableListOf<String>()
args.add(instruction[0])
val creator = CityBuilder()
var count: Int = 0
while (true) {
val value: String = nextValue(creator.getField(count))
try {
creator.setField(value, count)
args.add(value)
} catch (e: InvalidElementValueException) {
io.write(e.message + "\n")
continue
}
if (count == creator.size-1) break
count++
}
return args.toList()
} else if (!commands[instruction[0]]!!.validate(instruction.minus(instruction[0])))
throw InvalidElementValueException(instruction.minus(instruction[0]))
return instruction
}
fun getStandardCommands(): List<CommandWrapper> {
val help = CommandWrapper()
help.wrapCommand(HelpCommand(this))
val exit = CommandWrapper()
exit.wrapCommand(ExitCommand(this))
val script = CommandWrapper()
script.wrapCommand(ExecuteScriptCommand(this))
return listOf(help, exit, script)
}
}
@@ -0,0 +1,56 @@
package core
import commands.CommandWrapper
import exceptions.ConnectionException
import io.IOManager
import java.net.InetSocketAddress
import java.net.InetAddress
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel
import java.nio.channels.SelectionKey
import java.nio.channels.Selector
import kotlinx.serialization.json.Json.Default.encodeToString
import kotlinx.serialization.json.Json.Default.decodeFromString
class ConnectionManager(private val io: IOManager, private val host: InetAddress, private val port: Int) {
fun askCommandList(): List<CommandWrapper> {
val cw = CommandWrapper()
cw.name = "help"
val result = sendAndReceive(cw)
return decodeFromString(result.result)
}
fun sendAndReceive(cw: CommandWrapper): CommandWrapper {
val address: SocketAddress = InetSocketAddress(host, port)
val selector = Selector.open()
val channel = DatagramChannel.open()
val sendBuffer = ByteBuffer.wrap(encodeToString(cw).toByteArray())
val bytes = ByteArray(32768)
val receiveBuffer = ByteBuffer.wrap(bytes)
var result = CommandWrapper()
channel.configureBlocking(false)
val key = channel.register(selector, SelectionKey.OP_READ)
for (i in 1..3) {
sendBuffer.position(0)
channel.send(sendBuffer, address)
try {
selector.select(10000)
if (key.isReadable) {
channel.receive(receiveBuffer)
result = decodeFromString<CommandWrapper>(bytes.decodeToString().replace("\u0000", ""))
break
} else {
throw ConnectionException()
}
} catch (_: ConnectionException) {
if (i == 3) {
throw ConnectionException()
}
io.write("Не удалось установить соединение с сервером. Попытка ${i+1} из 3.\n")
continue
}
}
return result
}
}
@@ -0,0 +1,56 @@
package core
import exceptions.*
import io.IOManager
import java.io.IOException
/**
* Класс интерактивного взаимодействия с программой.
*
* Позволяет консольно взаимодействовать с программой и вводить команды через консоль..
*
* @constructor Принимает все описанные выше параметры.
*
* @since 1.0
*/
class InteractiveMode(private val io: IOManager, private val cm: ConnectionManager) {
private val ci: CommandInvoker = CommandInvoker(io, cm)
/**
* Запускает взаимодействие с программой.
*
* @since 1.0
*/
private fun interaction() {
var isWorking = true
ci.getCommands()
while (isWorking) {
try {
io.write("=> ")
ci.readCommand()
} catch (e: ProgramExitException) {
io.write(e.message + "\n")
isWorking = false
} catch (e: IOException) {
io.source = null
io.write("Ошибка чтения файла или записи в него.\n")
} catch (e: ConnectionException) {
io.write(e.message + "\n")
} catch (e: Exception) {
io.write("Возникла непредвиденная ошибка: " + e.message + "\n")
io.write("Экстренное завершение работы.\n")
isWorking = false
}
}
}
/**
* Запускает интерактивный режим.
*
* @since 1.0
*/
fun start() {
io.write("Программа запущена в интерактивном режиме. Чтобы увидеть список команд, введите help.\n")
interaction()
}
}
@@ -0,0 +1,50 @@
package io
import java.io.BufferedInputStream
import java.io.BufferedWriter
import java.io.FileInputStream
import java.io.FileWriter
/**
* Класс для чтения данных из файла.
*
* @param file Имя файла типа [String].
*
* @constructor Создаёт готовый к использованию объект, принимая все описанные выше параметры.
*
* @since 1.0
*/
class FileIO(private val file: String) {
/**
* Читает весь файл.
*
* @return [String] строку файла.
*
* @throws [NoSuchElementException] В случае, если в файле не осталось непрочитанных строк.
* @throws [java.io.IOException] В случае, если файла не существует.
*
* @since 1.0
*/
fun readFile(): String {
val reader = BufferedInputStream(FileInputStream(file))
val text = reader.readAllBytes().decodeToString()
reader.close()
return text
}
/**
* Записывает строки в файл.
*
* @param text Строки для записи типа [String].
*
* @throws [java.io.IOException] В случае, если файла не существует.
*
* @since 1.0
*/
fun writeToFile(text: String) {
val writer = BufferedWriter(FileWriter(file))
writer.write(text)
writer.close()
}
}
@@ -0,0 +1,83 @@
package io
import java.io.IOException
/**
* Класс для управления чтения и записи из файлов и консоли.
*
* @property source Путь к файлу, из которого читаются данные типа [String], по умолчанию null(консоль).
*
* @constructor Создаёт объект для взаимодействия с консолью.
*
* @since 1.0
*/
class IOManager() {
var source: String? = null
set(value) {
sourceType = if (value == "" || value == null) {
IOType.CONSOLE
} else {
IOType.FILE
}
if (sourceType == IOType.FILE) { io = FileIO(value!!) }
field = value
}
private var sourceType: IOType = IOType.CONSOLE
private lateinit var io: FileIO
/**
* Читает данные из [source].
*
* @return Строка из [source] типа [String].
*
* @throws [IOException] В случае, если данные не могут быть корректно прочитаны.
*
* @since 1.0
*/
fun read(): String {
val input: String = when (sourceType) {
IOType.CONSOLE -> readln()
IOType.FILE -> {
io.readFile()
}
}
return input
}
/**
* Записывает данные в [source].
*
* @param output Данные для записи типа [String].
*
* @throws [IOException] В случае, если данные не могут быть корректно записаны.
*
* @since 1.0
*/
fun write(output: String) {
when (sourceType) {
IOType.CONSOLE -> print(output)
IOType.FILE -> {
val io: FileIO = FileIO(source!!)
io.writeToFile(output)
}
}
}
/**
* Запрашивает ввод значения.
*
* @param value Строковое описание необходимого значения типа [String].
*
* @return Значение типа [String].
*
* @throws [IOException] В случае, если возникли проблемы с чтением/записью данных.
*
* @since 1.0
*/
fun askForValue(value: String): String {
val output = "Введите $value: "
write(output)
val text: String = read()
return text
}
}
@@ -0,0 +1,6 @@
package io
/**
* Типы возможных способов чтения данных, используемых программой.
*/
enum class IOType {FILE, CONSOLE}
@@ -0,0 +1,2 @@
group = 'com.leterzp.prog.general'
version = '2.0'
@@ -0,0 +1,76 @@
package commands
import core.CommandInvokerInterface
import exceptions.InvalidAmountOfArgumentsException
import kotlinx.serialization.Serializable
/**
* Абстрактный класс для всех команд.
*
* @property argumentsAmount Количество аргументов, которые принимает команда, типа [Int].
*
* @since 1.0
*/
abstract class Command(protected open val ci: CommandInvokerInterface) {
open val argumentsAmount: Int = 0
open var result: String = ""
override fun toString(): String {
if (getSyntax() == "") return getName() + getSyntax()
return getName() + " " + getSyntax()
}
/**
* Исполняет команду.
*
* @param arguments [List], содержащий в себе все аргументы команды типа [String].
*
* @throws InvalidAmountOfArgumentsException В случае, если количество данных элементов не совпадает с
* количеством элементов, которые принимает команда.
*
* @since 1.0
*/
open fun execute(arguments: List<String>) {
result = ""
var arguments = arguments
while (arguments.contains("")) arguments = arguments.minus("")
if (arguments.size != argumentsAmount) throw InvalidAmountOfArgumentsException(this, arguments.size)
}
/**
* Описывает команду.
*
* @return Описание команды типа [String].
*
* @since 1.0
*/
open fun describe(): String {
return "У этой команды нет описания"
}
/**
* Описывает синтаксис команды.
*
* @return Синтаксис команды типа [String].
*
* @since 1.0
*/
open fun getSyntax(): String {
return ""
}
/**
* Описывает имя команды.
*
* @return Имя команды типа [String].
*
* @since 1.0
*/
open fun getName(): String {
return "command"
}
open fun validate(arguments: List<String>, count: Int = 0): Boolean {
return true
}
}
@@ -0,0 +1,37 @@
package commands
import kotlinx.serialization.Serializable
@Serializable
class CommandWrapper() {
var command: String = ""
var argumentsAmount = 0
var arguments: List<String> = listOf("")
var string: String = ""
var describe: String = ""
var syntax: String = ""
var name: String = ""
var result: String = ""
fun wrapCommand(c: Command) {
command = c.getName()
argumentsAmount = c.argumentsAmount
string = c.toString()
describe = c.describe()
syntax = c.getSyntax()
name = c.getName()
}
fun validate(args: List<String>): Boolean {
if (args.size == argumentsAmount) return true
if (args.size == 1) {
try {
args[0].toLong()
return true
} catch (_: NumberFormatException) {
return false
}
}
return false
}
}
@@ -0,0 +1,28 @@
package commands
import core.CommandInvokerInterface
import exceptions.ProgramExitException
/**
* Команда для выхода из программы.
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @throws ProgramExitException В качестве сигнала завершения программы.
*
* @since 1.0
*/
class ExitCommand(ci: CommandInvokerInterface): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
throw ProgramExitException()
}
override fun describe(): String {
return "Завершает работу программы"
}
override fun getName(): String {
return "exit"
}
}
@@ -0,0 +1,7 @@
package core
import commands.CommandWrapper
interface CommandInvokerInterface {
fun executeCommand(cw: CommandWrapper): CommandWrapper
}
@@ -0,0 +1,105 @@
package elements
import exceptions.InvalidElementValueException
import java.time.LocalDate
import kotlinx.serialization.Serializable
import kotlinx.serialization.Contextual
/**
* Город.
*
* Элемент коллекции.
*
* @param name Название города типа [String].
* @param coordinates Координаты города типа [Coordinates].
* @param area Площадь города типа положительный [Double].
* @param population Население города типа положительный [Int].
* @param metersAboveSeaLevel Высота над уровнем моря типа [Long].
* @param populationDensity Плотность населения типа положительный [Float].
* @param governon Губернатор города типа [Human].
* @param climate Климат города типа [Climate], может быть null.
* @param government Правительство типа [Government], может быть null.
*
* @property id Уникальный номер города типа положительный [Long].
* @property creationDate Дата создания города типа [LocalDate].
*
* @constructor Принимает все параметры, описанные выше, создавая полноценный объект.
*
* @since 1.0
*/
@Serializable
class City(
val name1: String,
var coordinates: Coordinates,
val area1: Double,
val population1: Int,
var metersAboveSeaLevel: Long,
val populationDensity1: Float,
var governon: Human,
var climate: Climate? = null,
var government: Government? = null
): Comparable<City> {
var name: String = name1
set(value) {
if (value == "") throw InvalidElementValueException(value)
field = value
}
var area: Double = area1
set(value) {
if (value <= 0) throw InvalidElementValueException(value)
field = value
}
var population: Int = population1
set(value) {
if (value <= 0) throw InvalidElementValueException(value)
field = value
}
var populationDensity: Float = populationDensity1
set(value) {
if (value <= 0) throw InvalidElementValueException(value)
field = value
}
val id: Long = counter
@Contextual
val creationDate: LocalDate = LocalDate.now()
companion object { private var counter: Long = 1 }
init{
if (id >= counter) {
counter = id + 1
}
}
override fun compareTo(other: City): Int {
return this.id.compareTo(other.id)
}
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (other is City) {
if (this.id == other.id) return true
}
return false
}
override fun toString(): String {
var output: String = """
--$id: Город $name
был создан $creationDate
расположен по координатам $coordinates
$metersAboveSeaLevel метров над уровнем моря
занимает площадь $area
с населением $population и его плотностью $populationDensity
управляет им $governon
""".trimIndent()
if (government != null) {
output += "\n с правительством $government"
}
if (climate != null) {
output += "\n да и погода там $climate"
}
return output
}
}
@@ -0,0 +1,187 @@
package elements
import exceptions.InvalidElementValueException
/**
* Builder для класса [City].
*
* Может поэтапно создавать и изменять объекты класса [City].
*
* @property size Количество свойств типа [Int], необходимых для создания [City].
*
* @since 1.0
*/
class CityBuilder {
private var name: String? = null
set(value) {
if (value == null) throw InvalidElementValueException("")
field = value
}
private var coordinateX: Int? = null
set(value) {
if (value is Int && value > -827) field = value
else throw InvalidElementValueException(value?: "")
}
private var coordinateY: Double? = null
set(value) {
if (value == null) throw InvalidElementValueException("")
field = value
}
private var area: Double? = null
set(value) {
if (value is Double && value > 0) field = value
else throw InvalidElementValueException(value?: "")
}
private var population: Int? = null
set(value) {
if (value is Int && value > 0) field = value
else throw InvalidElementValueException(value?: "")
}
private var metersAboveSeaLevel: Long? = null
set(value) {
if (value == null) throw InvalidElementValueException("")
field = value
}
private var populationDensity: Float? = null
set(value) {
if (value is Float && value > 0) field = value
else throw InvalidElementValueException(value?: "")
}
private var govName: String? = null
set(value) {
if (value == null) throw InvalidElementValueException("")
field = value
}
private var govAge: Long? = null
set(value) {
if (value is Long && value > 0) field = value
else throw InvalidElementValueException(value?: "")
}
private var govHeight: Float? = null
set(value) {
if (value is Float && value > 0) field = value
else throw InvalidElementValueException(value?: "")
}
private var climate: Climate? = null
private var government: Government? = null
private val fields: Array<String> = arrayOf(
"название города (String)",
"координата X (Int)",
"координата Y (Double)",
"площадь (Double)",
"население (Int)",
"высоту над уровнем моря (Long)",
"плотность населения (Float)",
"имя губернатора (String)",
"возраст губернатора (Long)",
"рост губернатора (Float)",
"климат (RAIN_FOREST | MONSOON | HUMIDCONTINENTAL | SUBARCTIC | TUNDRA)",
"правительство (ARISTOCRACY | ANARCHY | KLEPTOCRACY | CORPORATOCRACY | JUNTA)"
)
val size: Int = 12
/**
* Используется для получения названия и типа свойства класса.
*
* @param count Номер свойства типа [Int].
*
* @return Имя и тип свойства типа [String].
*
* @since 1.0
*/
fun getField(count: Int): String {
return fields[count]
}
/**
* Используется для установки значения свойства.
*
* @param input Новое значение свойства типа [String].
* @param count Номер свойства типа [Int].
*
* @throws InvalidElementValueException В случае, если значение не может быть установлено.
*
* @since 1.0
*/
fun setField(input: String, count: Int) {
val value: String? = if (input == "") null else input
try {
when (count) {
0 -> name = value
1 -> coordinateX = value?.toInt()
2 -> coordinateY = value?.toDouble()
3 -> area = value?.toDouble()
4 -> population = value?.toInt()
5 -> metersAboveSeaLevel = value?.toLong()
6 -> populationDensity = value?.toFloat()
7 -> govName = value
8 -> govAge = value?.toLong()
9 -> govHeight = value?.toFloat()
10 -> climate = run {
val result: Climate? = if (value != null) Climate.valueOf(value.uppercase()) else null
result
}
11 -> government = run {
val result: Government? = if (value != null) Government.valueOf(value.uppercase()) else null
result
}
}
} catch (_: NumberFormatException) {
throw InvalidElementValueException(value?:"")
} catch (_: IllegalArgumentException) {
throw InvalidElementValueException(value?:"")
}
}
/**
* Создаёт объект типа [City] на основе загруженных полей.
*
* @return Город типа [City].
*
* @throws InvalidElementValueException В случае, если какое-либо из установленных значений не подходит под
* требования класса [City].
*
* @since 1.0
*/
fun create(): City {
if (name != null
&& coordinateX != null
&& coordinateY != null
&& area != null
&& population != null
&& metersAboveSeaLevel != null
&& populationDensity != null
&& govName != null
&& govAge != null
&& govHeight != null
) {
val cords = Coordinates(coordinateX!!, coordinateY!!)
val governon = Human(govName!!, govAge!!, govHeight!!)
return City(name!!, cords, area!!,
population!!, metersAboveSeaLevel!!, populationDensity!!,
governon, climate, government)
} else throw InvalidElementValueException("City")
}
/**
* Изменяет свойства уже имеющегося объекта типа [City].
*
* @param city Город для изменения типа [City].
*
* @since 1.0
*/
fun update(city: City) {
if (name != null) city.name = name!!
if (coordinateX != null && coordinateY != null)
city.coordinates = Coordinates(coordinateX!!, coordinateY!!)
if (area != null) city.area = area!!
if (population != null) city.population = population!!
if (metersAboveSeaLevel != null) city.metersAboveSeaLevel = metersAboveSeaLevel!!
if (populationDensity != null) city.populationDensity = populationDensity!!
if (govName != null && govAge != null && govHeight != null)
city.governon = Human(govName!!, govAge!!, govHeight!!)
if (climate != null) city.climate = climate
if (government != null) city.government = government
}
}
@@ -0,0 +1,6 @@
package elements
/**
* Перечисление возможных климатов для города [City].
*/
enum class Climate {RAIN_FOREST, MONSOON, HUMIDCONTINENTAL, SUBARCTIC, TUNDRA}
@@ -0,0 +1,28 @@
package elements
import exceptions.InvalidElementValueException
import kotlinx.serialization.Serializable
/**
* Координаты для города [City].
*
* @param x Координата по оси X типа [Int] больше -827.
* @param y Координата по оси Y типа [Double].
*
* @throws InvalidElementValueException В случае, если координаты не соответствуют необходимым требованиям.
*
* @constructor Принимает все указанные выше параметры, создавая готовый к использованию
* объект с заданными координатами.
*
* @since 1.0
*/
@Serializable
class Coordinates(private val x: Int, private val y: Double) {
init{
if(x <= -827) throw InvalidElementValueException(x)
}
override fun toString(): String {
return "x = $x, y = $y"
}
}
@@ -0,0 +1,6 @@
package elements
/**
* Перечисления возможных видов правительства для города [City].
*/
enum class Government{ ARISTOCRACY, ANARCHY, KLEPTOCRACY, CORPORATOCRACY, JUNTA}
@@ -0,0 +1,24 @@
package elements
import exceptions.InvalidElementValueException
import kotlinx.serialization.Serializable
/**
* Человек для города [City].
*
* @param name
* @param age
* @param height
*/
@Serializable
class Human(private val name: String, private val age: Long, private val height: Float) {
init{
if (name == "") throw InvalidElementValueException(name)
if (age <= 0) throw InvalidElementValueException(age)
if (height <= 0) throw InvalidElementValueException(height)
}
override fun toString(): String {
return "$name, возраст $age, рост $height"
}
}
@@ -0,0 +1,18 @@
package exceptions
/**
* Исключение, показывающее, что в коллекции нет элемента с таким [id][elements.City.id].
*
* Наследуется от класса [Exception].
*
* @param id [id] элемента типа [id][elements.City.id].
*
* @property message Сообщение об ошибке типа [String].
*
* @constructor Принимает все перечисленные выше параметры.
*
* @since 1.0
*/
class CollectionHasNoElementException(private val id: Long): Exception() {
override val message: String = "У коллекции нет элемента с id $id"
}
@@ -0,0 +1,18 @@
package exceptions
/**
* Исключение, показывающее, что исполняемая команда не найдена.
*
* Наследуется от класса [Exception].
*
* @param command Выполняемая [command] типа [Command][commands.Command].
*
* @property message Сообщение об ошибке типа [String].
*
* @constructor Принимает все перечисленные выше параметры.
*
* @since 1.0
*/
class CommandNotFoundException(private val command: String): Exception() {
override val message: String = "Команда $command не найдена"
}
@@ -0,0 +1,5 @@
package exceptions
class ConnectionException(): Exception() {
override val message: String = "Не удалось установить соединение."
}
@@ -0,0 +1,24 @@
package exceptions
import commands.Command
/**
* Исключение, показывающее, что количество полученных командой аргументов отличается от ожидаемого.
*
* Наследуется от класса [Exception].
*
* @param command Выполняемая [Command][commands.Command].
* @param amount Количество полученных аргументов типа [Int].
*
* @property message Сообщение об ошибке типа [String].
*
* @constructor Принимает все перечисленные выше параметры.
*
* @since 1.0
*/
class InvalidAmountOfArgumentsException(private val command: Command, private val amount: Int): Exception() {
private val name: String = command.getName()
private val realAmount: Int = command.argumentsAmount
override val message: String = "Команда $name принимает только $realAmount аргументов, а не $amount"
}
@@ -0,0 +1,18 @@
package exceptions
/**
* Исключение, показывающее, что данное значение элемента не может быть установлено.
*
* Наследуется от класса [Exception].
*
* @param value Полученное значение.
*
* @property message Сообщение об ошибке типа [String].
*
* @constructor Принимает все перечисленные выше параметры.
*
* @since 1.0
*/
class InvalidElementValueException(private val value: Any): Exception() {
override val message: String = "Значение '$value' не может быть задано"
}
@@ -0,0 +1,16 @@
package exceptions
/**
* Исключение, показывающее, что очередь на выполнение команд пуста.
*
* Наследуется от класса [Exception].
*
* @property message Сообщение об ошибке типа [String].
*
* @constructor Стандартный конструктор класса [Exception].
*
* @since 1.0
*/
class NoNextCommandException: Exception() {
override val message: String = "Чтение скрипта окончено"
}
@@ -0,0 +1,16 @@
package exceptions
/**
* Исключение, показывающее, что программа завершила работу.
*
* Наследуется от класса [Exception].
*
* @property message Сообщение об ошибке типа [String].
*
* @constructor Стандартный конструктор класса [Exception].
*
* @since 1.0
*/
class ProgramExitException(): Exception() {
override val message: String = "Завершение работы."
}
+1
View File
@@ -0,0 +1 @@
kotlin.code.style=official
Binary file not shown.
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+16
View File
@@ -0,0 +1,16 @@
group = 'com.leterzp.prog.server'
version = '2.0'
dependencies {
implementation project(':general')
}
apply plugin: 'com.gradleup.shadow'
shadowJar {
archiveBaseName.set('ProgLab6.server')
archiveVersion.set('2.0')
manifest {
attributes 'Main-Class': 'ServerKt'
}
}
@@ -0,0 +1,10 @@
import core.ServerInitializer
import io.IOManager
fun main() {
val port = 8841
val io = IOManager()
val si = ServerInitializer(port, io)
si.initialize()
si.waitForExit()
}
@@ -0,0 +1,50 @@
package commands
import core.CommandInvoker
import elements.CityBuilder
import exceptions.InvalidAmountOfArgumentsException
import exceptions.InvalidElementValueException
/**
* Команда для добавления элемента в коллекцию.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class AddCommand(override val ci: CommandInvoker): Command(ci) {
override val argumentsAmount = CityBuilder().size
override fun execute(arguments: List<String>) {
val creator = CityBuilder()
if (arguments.size != creator.size) throw InvalidAmountOfArgumentsException(this, arguments.size)
var count = 0
try {
while (true) {
creator.setField(arguments[count], count)
if (count == creator.size-1) break
count++
}
ci.cm.addElement(creator.create())
ci.io.logger.info("Элемент успешно добавлен.")
result = "Элемент успешно добавлен.\n"
} catch (e: InvalidElementValueException) {
result = e.message + "\n"
ci.io.logger.warning(e.message)
}
}
override fun getSyntax(): String {
return "{element}"
}
override fun getName(): String {
return "add"
}
override fun describe(): String {
return "Добавляет элемент в коллекцию"
}
}
@@ -0,0 +1,29 @@
package commands
import core.CommandInvoker
/**
* Команда для очистки коллекции.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class ClearCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
ci.cm.clearCollection()
result = "Коллекция отчищена.\n"
ci.io.logger.info("Коллекция отчищена.")
}
override fun describe(): String {
return "Удаляет все элементы коллекции"
}
override fun getName(): String {
return "clear"
}
}
@@ -0,0 +1,39 @@
package commands
import core.CommandInvoker
/**
* Команда для вывода количества элементов коллекции, высота над уровнем моря которых больше заданного.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class CountGreaterThenMetersAboveSeaLevelCommand(override val ci: CommandInvoker): Command(ci) {
override val argumentsAmount: Int = 1
override fun execute(arguments: List<String>) {
super.execute(arguments)
try {
result = "Количество: " + ci.cm.countHigherThen(arguments[0].toLong()) + "\n"
ci.io.logger.info("Найдено количество элементов выше ${arguments[0]} метров.")
} catch (e: NumberFormatException) {
result = "Невозможно сравнить с данным значением, оно должно быть типа Long.\n"
ci.io.logger.warning("Неверный тип значения ${arguments[0]}.")
}
}
override fun describe(): String {
return "Выводит количество элементов выше заданного уровня моря"
}
override fun getSyntax(): String {
return "[metersAboveSeaLevel]"
}
override fun getName(): String {
return "count_greater_then_meters_above_sea_level"
}
}
@@ -0,0 +1,32 @@
package commands
import core.CommandInvoker
/**
* Команда для группировки по имени с подсчётом повторений.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class GroupCountingByNameCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
val names: HashMap<String, Int> = ci.cm.groupElements()
result = "Названия городов:\n"
for (name in names) {
result += " --${name.key}: ${name.value}\n"
}
ci.io.logger.info("Найден список всех элементов.")
}
override fun describe(): String {
return "Выводит количество элементов, сгруппированных по названиям"
}
override fun getName(): String {
return "group_counting_by_name"
}
}
@@ -0,0 +1,33 @@
package commands
import core.CommandInvoker
/**
* Команда для получения информации о коллекции.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class InfoCommand(override val ci: CommandInvoker): Command(ci) {
override fun describe(): String {
return "Выводит всю информацию о коллекции"
}
override fun execute(arguments: List<String>) {
super.execute(arguments)
val time = ci.cm.initializationTime
val size = ci.cm.size()
result = "Информация о коллекции:\n"
result += " --Тип коллекции: java.util.Stack\n"
result += " --Дата инициализации коллекции: $time\n"
result += " --Количество элементов в коллекции: $size\n"
ci.io.logger.info("Найдена информация о коллекции.")
}
override fun getName(): String {
return "info"
}
}
@@ -0,0 +1,18 @@
package commands
import core.CommandInvoker
class PingCommand(override val ci: CommandInvoker): Command(ci) {
override fun describe(): String {
return "ping"
}
override fun execute(arguments: List<String>) {
super.execute(arguments)
result = "ping\n"
}
override fun getName(): String {
return "ping"
}
}
@@ -0,0 +1,33 @@
package commands
import core.CommandInvoker
import elements.Government
/**
* Команда для получения всех свойств вида [Government] в сортированном виде.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class PrintFieldAscendingGovernmentCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
val governments: List<Government?> = ci.cm.getSortedGovernments()
result = ""
for (element in governments) {
result += element.toString()+ "\n"
}
ci.io.logger.info("Найдены все правительства.")
}
override fun describe(): String {
return "Выводит правительства городов в порядке возрастания"
}
override fun getName(): String {
return "print_field_ascending_government"
}
}
@@ -0,0 +1,43 @@
package commands
import core.CommandInvoker
/**
* Команда для удаления элемента по id.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class RemoveByIdCommand(override val ci: CommandInvoker): Command(ci) {
override val argumentsAmount: Int = 1
override fun execute(arguments: List<String>) {
super.execute(arguments)
var value: Long
try {
value = arguments[0].toLong()
ci.cm.removeElement(value)
result = "Элемент $value успешно удалён.\n"
ci.io.logger.info("Элемент $value удалён.")
}
catch (e: NumberFormatException) {
result = "${arguments[0]} не является id элемента.\n"
ci.io.logger.warning("Неверный id элемента.")
}
}
override fun describe(): String {
return "Удаляет элемент по его id"
}
override fun getSyntax(): String {
return "[id]"
}
override fun getName(): String {
return "remove_by_id"
}
}
@@ -0,0 +1,40 @@
package commands
import core.CommandInvoker
/**
* Команда для удаления элементов с [id][elements.City.id] выше заданного.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class RemoveGreaterCommand(override val ci: CommandInvoker): Command(ci) {
override val argumentsAmount: Int = 1
override fun execute(arguments: List<String>) {
super.execute(arguments)
try {
val count: Int = ci.cm.removeGreater(arguments[0].toLong())
result = "Удалено $count элементов.\n"
ci.io.logger.info("Удалено $count элементов.")
} catch (e: NumberFormatException) {
result = "${arguments[0]} не является id элемента.\n"
ci.io.logger.warning("Неверный id элемента.")
}
}
override fun describe(): String {
return "Удаляет элементы коллекции больше заданного"
}
override fun getSyntax(): String {
return "[id]"
}
override fun getName(): String {
return "remove_greater"
}
}
@@ -0,0 +1,35 @@
package commands
import core.CommandInvoker
import exceptions.CollectionHasNoElementException
/**
* Команда для удаления последнего элемента коллекции.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class RemoveLastCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
try {
ci.cm.removeLast()
result = "Элемент успешно удалён.\n"
ci.io.logger.info("Последний элемент удалён.")
} catch (e: CollectionHasNoElementException) {
result = "Последний элемент не найден: коллекция пуста.\n"
ci.io.logger.warning("Коллекция пуста.")
}
}
override fun describe(): String {
return "Удаляет последний элемент коллекции"
}
override fun getName(): String {
return "remove_last"
}
}
@@ -0,0 +1,29 @@
package commands
import core.CommandInvoker
/**
* Команда для переворота коллекции.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class ReorderCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
ci.cm.reorderElements()
result = "Коллекция успешно перевёрнута.\n"
ci.io.logger.info("Коллекция перевернута.")
}
override fun describe(): String {
return "Переворачивает коллекцию"
}
override fun getName(): String {
return "reorder"
}
}
@@ -0,0 +1,35 @@
package commands
import core.CommandInvoker
import java.io.IOException
/**
* Команда для сохранения коллекции в файл.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class SaveCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
try {
ci.cm.saveToFile()
result = "Коллекция успешно сохранена.\n"
ci.io.logger.info("Коллекция сохранена.")
} catch (e: IOException) {
result = "Файл сохранения не найден. Коллекция не сохранена.\n"
ci.io.logger.warning("Файл сохранения не найден.")
}
}
override fun describe(): String {
return "Сохраняет коллекцию в файл"
}
override fun getName(): String {
return "save"
}
}
@@ -0,0 +1,30 @@
package commands
import core.CommandInvoker
/**
* Команда для получения информации об элементах коллекции.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class ShowCommand(override val ci: CommandInvoker): Command(ci) {
override fun execute(arguments: List<String>) {
super.execute(arguments)
val output: String = ci.cm.getAllElementsToString()
if (output != "") result = output + "\n"
else result = "Коллекция пуста.\n"
ci.io.logger.info("Список элементов найден.")
}
override fun describe(): String {
return "Выводит список всех элементов коллекции"
}
override fun getName(): String {
return "show"
}
}
@@ -0,0 +1,60 @@
package commands
import core.CommandInvoker
import elements.CityBuilder
import exceptions.InvalidAmountOfArgumentsException
import exceptions.InvalidElementValueException
/**
* Команда для обновления элемента коллекции.
*
* @param ci [CommandInvoker] для [Command].
*
* @constructor Вызывает родительский конструктор класса [Command].
*
* @since 1.0
*/
class UpdateCommand(override val ci: CommandInvoker): Command(ci) {
override val argumentsAmount: Int = 1
override fun execute(arguments: List<String>) {
var arguments = arguments
val id: Long
try {
id = arguments[0].toLong()
} catch (_: NumberFormatException) {
result = "${arguments[0]} не является id элемента.\n"
ci.io.logger.warning("Элемент ${arguments[0]} не найден.")
return
}
arguments = arguments.minus(arguments[0])
val creator = CityBuilder()
if (arguments.size != creator.size) throw InvalidAmountOfArgumentsException(this, arguments.size)
var count = 0
try {
while (true) {
creator.setField(arguments[count], count)
if (count == creator.size-1) break
count++
}
creator.update(ci.cm.getElement(id))
result = "Элемент успешно обновлён.\n"
ci.io.logger.info("Элемент обновлён.")
} catch (e: InvalidElementValueException) {
result = e.message + "\n"
ci.io.logger.warning(e.message)
}
}
override fun describe(): String {
return "Обновляет значение элемента"
}
override fun getSyntax(): String {
return "[id] {element}"
}
override fun getName(): String {
return "update"
}
}
@@ -0,0 +1,243 @@
package core
import elements.City
import elements.Government
import exceptions.CollectionHasNoElementException
import io.IOManager
import java.io.IOException
import java.time.LocalDate
import java.util.Stack
import java.util.stream.Collectors
/**
* Класс для управления коллекцией, содержащей элементы типа [City].
*
* @param io [IOManager], с которым взаимодействует коллекция.
*
* @property initializationTime Время инициализации коллекции типа [LocalDate].
*
* @constructor Принимает все указанные выше параметры, создавая готовый к использованию объект
* и сразу загружая элементы из файла.
*
* @since 1.0
*/
class CollectionManager(val io: IOManager) {
private val save: String = System.getenv("SAVE_FILE") ?: "save.json"
private var collection: Stack<City> = Stack<City>()
val initializationTime: LocalDate = LocalDate.now()
init {
try {
collection = io.readJsonFile(save)
io.logger.info("Коллекция успешно загружена.")
} catch (e: IOException) {
io.logger.warning("Файл сохранения не найден.")
}
}
/**
* Считает количество элементов коллекции.
*
* @return Количество элементов коллекции типа [Int].
*
* @since 1.0
*/
fun size(): Int {
io.logger.info("Поиск длины коллекции...")
return collection.stream().count().toInt()
}
/**
* Сохраняет хранящиеся в коллекции объекты в файл.
*
* @throws [java.io.IOException] В случае ошибки доступа к файлу.
*
* @since 1.0
*/
fun saveToFile() {
io.logger.info("Сохранение коллекции...")
io.writeJsonFile(save, collection)
}
/**
* Сортирует элементы коллекции.
*
* @since 1.0
*/
fun sortElements() {
io.logger.info("Сортировка коллекции...")
collection.sort()
}
/**
* Добавляет элемент в коллекцию.
*
* @param city Город типа [City], который нужно добавить в коллекцию.
*
* @since 1.0
*/
fun addElement(city: City) {
io.logger.info("Добавление элемента...")
collection.push(city)
}
/**
* Переворачивает коллекцию.
*
* @since 1.0
*/
fun reorderElements() {
io.logger.info("Переворачивание коллекции...")
val newCollection: Stack<City> = Stack<City>()
for (element in collection.asReversed()) {
newCollection.push(element)
}
collection = newCollection
}
/**
* Считает количество элементов коллекции, высота над уровнем моря которого выше заданного.
*
* @param metersAboveSeaLevel Высота над уровнем моря типа [Long].
*
* @return Количество элементов коллекции, подходящих по условию, типа [Int].
*
* @since 1.0
*/
fun countHigherThen(metersAboveSeaLevel: Long): Int {
io.logger.info("Поиск элементов выше заданного значения...")
return collection.stream()
.filter { x -> x.metersAboveSeaLevel > metersAboveSeaLevel }
.count()
.toInt()
}
/**
* Выдаёт элемент из коллекции по [id][City.id].
*
* @param id [id][City.id] города [City].
*
* @return Элемент коллекции типа [City].
*
* @throws exceptions.CollectionHasNoElementException В случае, если в коллекции нет элемента с таким [id].
*
* @since 1.0
*/
fun getElement(id: Long): City {
io.logger.info("Поиск элемента...")
try {
return collection.stream()
.filter { x -> x.id == id }
.collect(Collectors.toList())[0]
} catch (e: IndexOutOfBoundsException) {
throw CollectionHasNoElementException(id)
}
}
/**
* Выдаёт все элементы коллекции в строковом представлении.
*
* @return Все элементы коллекции типа [String].
*
* @since 1.0
*/
fun getAllElementsToString(): String {
io.logger.info("Поиск всех элементов...")
return collection.stream()
.map { city -> city.toString() }
.collect(Collectors.toList())
.joinToString("\n")
}
/**
* Выдаёт все виды правительств элементов коллекции в сортированном виде.
*
* @return [ArrayList], содержащий все виды правительств типа [Government].
*
* @since 1.0
*/
fun getSortedGovernments(): List<Government?> {
io.logger.info("Поиск всех правительств...")
return collection.stream()
.sorted { city1, city2 -> compareValues(city1.government, city2.government) }
.map { city -> city.government }
.collect(Collectors.toList())
}
/**
* Выдаёт сгруппированные имена всех городов и количество городов с одинаковым именем.
*
* @return [HashMap] с парами элементов имя типа [String] и количество типа [Int].
*
* @since 1.0
*/
fun groupElements(): HashMap<String, Int> {
io.logger.info("Группировка элементов по именам...")
val names: HashMap<String, Int> = HashMap()
val list = collection.stream()
.map { city -> city.name }
.collect(Collectors.toList())
for (name in list) {
names[name] = names[name] ?: 0
names[name] = names[name]!! + 1
}
return names
}
/**
* Удаляет элемент из коллекции по [id][City.id].
*
* @param id [id][City.id] города [City].
*
* @throws exceptions.CollectionHasNoElementException В случае, если в коллекции нет элемента с таким [id].
*
* @since 1.0
*/
fun removeElement(id: Long) {
io.logger.info("Удаление элемента...")
if (!collection.remove(this.getElement(id))) throw CollectionHasNoElementException(id)
}
/**
* Удаляет последний элемент коллекции.
*
* @throws exceptions.CollectionHasNoElementException В случае, если коллекция пуста.
*
* @since 1.0
*/
fun removeLast() {
io.logger.info("Удаление последнего элемента...")
if (collection.empty()) throw CollectionHasNoElementException(-1)
collection.remove(collection.last())
}
/**
* Удаляет элементы, [id][City.id] которых больше заданного.
*
* @param id [id][City.id] города [City].
*
* @return Количество типа [Int] удалённых элементов.
*
* @since 1.0
*/
fun removeGreater(id: Long): Int {
io.logger.info("Удаление всех элементов больше заданного...")
val list = collection.stream()
.filter {city -> city.id > id}
.collect(Collectors.toList())
for (element in list) {
collection.remove(element)
}
return list.size
}
/**
* Удаляет все элементы коллекции.
*
* @since 1.0
*/
fun clearCollection() {
io.logger.info("Отчистка коллекции...")
collection.clear()
}
}
@@ -0,0 +1,74 @@
package core
import commands.*
import exceptions.CommandNotFoundException
class CommandInvoker(val cm: CollectionManager): CommandInvokerInterface {
val commands: HashMap<String, Command> = HashMap()
val io = cm.io
init {
initializeCommand(InfoCommand(this))
initializeCommand(ShowCommand(this))
initializeCommand(AddCommand(this))
initializeCommand(UpdateCommand(this))
initializeCommand(RemoveByIdCommand(this))
initializeCommand(ClearCommand(this))
initializeCommand(RemoveLastCommand(this))
initializeCommand(RemoveGreaterCommand(this))
initializeCommand(ReorderCommand(this))
initializeCommand(GroupCountingByNameCommand(this))
initializeCommand(CountGreaterThenMetersAboveSeaLevelCommand(this))
initializeCommand(PrintFieldAscendingGovernmentCommand(this))
initializeCommand(PingCommand(this))
}
/**
* Инициализирует команду, добавляя её в список возможных к использованию.
*
* @param command Команда для инициализации, типа [Command].
*
* @since 1.0
*/
fun initializeCommand(command: Command) {
val name: String = command.getName()
commands[name] = command
io.logger.info("Команда $name инициализирована.")
}
fun runOnServer(command: String) {
when (command.trim()) {
"exit" -> {
runOnServer("save")
val exit = ExitCommand(this)
io.logger.info("Выполняется выход.")
exit.execute(listOf(""))
}
"save" -> {
val save = SaveCommand(this)
io.logger.info("Выполняется сохранение.")
save.execute(listOf(""))
}
else -> return
}
}
override fun executeCommand(cw: CommandWrapper): CommandWrapper {
val command = commands[cw.command] ?: throw CommandNotFoundException(cw.command)
io.logger.info("Выполняется команда ${cw.name}.")
command.execute(cw.arguments)
cw.result = command.result
return cw
}
fun getAllCommandsWrapped(): List<CommandWrapper> {
val list = mutableListOf<CommandWrapper>()
for (command in commands.values) {
val wrapper = CommandWrapper()
wrapper.wrapCommand(command)
list.add(wrapper)
}
io.logger.info("Подготовлен список команд.")
return list.toList()
}
}
@@ -0,0 +1,56 @@
package core
import commands.CommandWrapper
import exceptions.ProgramExitException
import java.net.DatagramPacket
import java.net.DatagramSocket
import kotlinx.serialization.json.Json.Default.decodeFromString
import kotlinx.serialization.json.Json.Default.encodeToString
import java.net.InetAddress
class ConnectionReceiver(private val ci: CommandInvoker, private val port: Int) {
private var isWorking = true
private var waitingForCommand = false
private val socket = DatagramSocket(port, InetAddress.getLocalHost())
private var bytes = ByteArray(32768)
private val io = ci.io
fun checkConnection() {
if (!isWorking) throw ProgramExitException()
bytes = ByteArray(32768)
val packet = DatagramPacket(bytes, bytes.size)
waitingForCommand = true
socket.receive(packet)
waitingForCommand = false
if (isWorking) receive(packet.address, packet.port)
}
private fun receive(host: InetAddress, port: Int) {
io.logger.info("Запрос получен.")
var cw: CommandWrapper = decodeFromString(bytes.decodeToString().replace("\u0000", ""))
val result: String
if (cw.name == "help") {
val list = ci.getAllCommandsWrapped()
cw.result = encodeToString(list)
} else {
cw = ci.executeCommand(cw)
}
result = encodeToString(cw)
io.logger.info("Результат загружен.")
send(host, port, result)
}
private fun send(host: InetAddress, port: Int, response: String) {
bytes = response.toByteArray()
val packet = DatagramPacket(bytes, bytes.size, host, port)
socket.send(packet)
io.logger.info("Результат отправлен.")
}
fun saveInterrupt() {
isWorking = false
if (waitingForCommand) {
send(socket.localAddress, port, "exit")
}
}
}
@@ -0,0 +1,45 @@
package core
import exceptions.ProgramExitException
import io.IOManager
class ServerInitializer(val port: Int, val io: IOManager) {
val cm = CollectionManager(io)
val ci = CommandInvoker(cm)
val cr = ConnectionReceiver(ci, port)
lateinit var server: Thread
lateinit var client: Thread
fun initialize() {
server = Thread {
var isWorking = true
while (isWorking) {
try {
ci.runOnServer(io.readLocalCommands())
} catch (e: ProgramExitException) {
isWorking = false
}
}
}
client = Thread {
var isWorking = true
while (isWorking) {
try {
cr.checkConnection()
} catch (_: ProgramExitException) {
isWorking = false
}
}
}
server.start()
client.start()
io.logger.info("Сервер запущен.")
}
fun waitForExit() {
if (!server.isAlive && !client.isAlive) return
server.join()
cr.saveInterrupt()
io.logger.info("Завершение работы.")
}
}
@@ -0,0 +1,48 @@
package io
import elements.City
import java.io.BufferedInputStream
import java.io.BufferedWriter
import java.io.FileInputStream
import java.io.FileWriter
import java.util.Stack
import java.util.logging.FileHandler
import java.util.logging.Logger
import java.util.logging.SimpleFormatter
import kotlinx.serialization.json.Json.Default.encodeToString
import kotlinx.serialization.json.Json.Default.decodeFromString
class IOManager {
val logger: Logger = Logger.getLogger("server")
init {
logger.addHandler(FileHandler("logs"))
logger.handlers[0].formatter = SimpleFormatter()
}
fun readJsonFile(file: String): Stack<City> {
val reader = BufferedInputStream(FileInputStream(file))
val text = reader.readAllBytes().decodeToString()
reader.close()
val decodedStack: Stack<City> = Stack<City>()
if (text != "") {
val decodedList: List<City> = decodeFromString<List<City>>(text)
for (element in decodedList) {
decodedStack.push(element)
}
}
return decodedStack
}
fun writeJsonFile(file: String, stack: Stack<City>) {
val listToEncode: List<City> = stack.toList()
val text: String = encodeToString(listToEncode)
val writer = BufferedWriter(FileWriter(file))
writer.write(text)
writer.close()
}
fun readLocalCommands(): String {
return readln()
}
}
+7
View File
@@ -0,0 +1,7 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}
rootProject.name = 'ProgLab6'
include 'general'
include 'client'
include 'server'