diff --git a/Programming/ProgLab6/.gitignore b/Programming/ProgLab6/.gitignore new file mode 100644 index 0000000..0bf5616 --- /dev/null +++ b/Programming/ProgLab6/.gitignore @@ -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 \ No newline at end of file diff --git a/Programming/ProgLab6/.idea/.gitignore b/Programming/ProgLab6/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/Programming/ProgLab6/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/Programming/ProgLab6/.idea/gradle.xml b/Programming/ProgLab6/.idea/gradle.xml new file mode 100644 index 0000000..83fefe0 --- /dev/null +++ b/Programming/ProgLab6/.idea/gradle.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/Programming/ProgLab6/.idea/kotlinc.xml b/Programming/ProgLab6/.idea/kotlinc.xml new file mode 100644 index 0000000..42dd882 --- /dev/null +++ b/Programming/ProgLab6/.idea/kotlinc.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/Programming/ProgLab6/.idea/misc.xml b/Programming/ProgLab6/.idea/misc.xml new file mode 100644 index 0000000..97740da --- /dev/null +++ b/Programming/ProgLab6/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Programming/ProgLab6/.idea/vcs.xml b/Programming/ProgLab6/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/Programming/ProgLab6/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Programming/ProgLab6/build.gradle b/Programming/ProgLab6/build.gradle new file mode 100644 index 0000000..29ad40c --- /dev/null +++ b/Programming/ProgLab6/build.gradle @@ -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) + } +} + diff --git a/Programming/ProgLab6/client/build.gradle b/Programming/ProgLab6/client/build.gradle new file mode 100644 index 0000000..dbf5b77 --- /dev/null +++ b/Programming/ProgLab6/client/build.gradle @@ -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' + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/Client.kt b/Programming/ProgLab6/client/src/main/kotlin/Client.kt new file mode 100644 index 0000000..5c952f0 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/Client.kt @@ -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") + } + +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/commands/ExecuteScriptCommand.kt b/Programming/ProgLab6/client/src/main/kotlin/commands/ExecuteScriptCommand.kt new file mode 100644 index 0000000..80f8cd8 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/commands/ExecuteScriptCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/commands/HelpCommand.kt b/Programming/ProgLab6/client/src/main/kotlin/commands/HelpCommand.kt new file mode 100644 index 0000000..e991396 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/commands/HelpCommand.kt @@ -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) { + 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 "[ | command]" + } + + override fun describe(): String { + return "Выводит информацию о всех командах либо описание одной команды" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/core/CommandInvoker.kt b/Programming/ProgLab6/client/src/main/kotlin/core/CommandInvoker.kt new file mode 100644 index 0000000..408a071 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/core/CommandInvoker.kt @@ -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 = HashMap() + private val nextArgument: Stack = Stack() + val executionHistory = Stack() + + 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 = 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() + 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 = 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): List { + if (command.argumentsAmount > 1) { + val args = mutableListOf() + 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 { + 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) + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/core/ConnectionManager.kt b/Programming/ProgLab6/client/src/main/kotlin/core/ConnectionManager.kt new file mode 100644 index 0000000..f3656cf --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/core/ConnectionManager.kt @@ -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 { + 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(bytes.decodeToString().replace("\u0000", "")) + break + } else { + throw ConnectionException() + } + } catch (_: ConnectionException) { + if (i == 3) { + throw ConnectionException() + } + io.write("Не удалось установить соединение с сервером. Попытка ${i+1} из 3.\n") + continue + } + } + return result + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/core/InteractiveMode.kt b/Programming/ProgLab6/client/src/main/kotlin/core/InteractiveMode.kt new file mode 100644 index 0000000..956de23 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/core/InteractiveMode.kt @@ -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() + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/io/FileIO.kt b/Programming/ProgLab6/client/src/main/kotlin/io/FileIO.kt new file mode 100644 index 0000000..1d746e4 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/io/FileIO.kt @@ -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() + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/io/IOManager.kt b/Programming/ProgLab6/client/src/main/kotlin/io/IOManager.kt new file mode 100644 index 0000000..afbaac7 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/io/IOManager.kt @@ -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 + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/client/src/main/kotlin/io/IOType.kt b/Programming/ProgLab6/client/src/main/kotlin/io/IOType.kt new file mode 100644 index 0000000..d497c78 --- /dev/null +++ b/Programming/ProgLab6/client/src/main/kotlin/io/IOType.kt @@ -0,0 +1,6 @@ +package io + +/** + * Типы возможных способов чтения данных, используемых программой. + */ +enum class IOType {FILE, CONSOLE} \ No newline at end of file diff --git a/Programming/ProgLab6/general/build.gradle b/Programming/ProgLab6/general/build.gradle new file mode 100644 index 0000000..a196c81 --- /dev/null +++ b/Programming/ProgLab6/general/build.gradle @@ -0,0 +1,2 @@ +group = 'com.leterzp.prog.general' +version = '2.0' \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/commands/Command.kt b/Programming/ProgLab6/general/src/main/kotlin/commands/Command.kt new file mode 100644 index 0000000..a6af759 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/commands/Command.kt @@ -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) { + 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, count: Int = 0): Boolean { + return true + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/commands/CommandWrapper.kt b/Programming/ProgLab6/general/src/main/kotlin/commands/CommandWrapper.kt new file mode 100644 index 0000000..90a8165 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/commands/CommandWrapper.kt @@ -0,0 +1,37 @@ +package commands + +import kotlinx.serialization.Serializable + +@Serializable +class CommandWrapper() { + var command: String = "" + var argumentsAmount = 0 + var arguments: List = 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): Boolean { + if (args.size == argumentsAmount) return true + if (args.size == 1) { + try { + args[0].toLong() + return true + } catch (_: NumberFormatException) { + return false + } + } + return false + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/commands/ExitCommand.kt b/Programming/ProgLab6/general/src/main/kotlin/commands/ExitCommand.kt new file mode 100644 index 0000000..de27ea2 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/commands/ExitCommand.kt @@ -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) { + super.execute(arguments) + throw ProgramExitException() + } + + override fun describe(): String { + return "Завершает работу программы" + } + + override fun getName(): String { + return "exit" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/core/CommandInvokerInterface.kt b/Programming/ProgLab6/general/src/main/kotlin/core/CommandInvokerInterface.kt new file mode 100644 index 0000000..5e8008b --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/core/CommandInvokerInterface.kt @@ -0,0 +1,7 @@ +package core + +import commands.CommandWrapper + +interface CommandInvokerInterface { + fun executeCommand(cw: CommandWrapper): CommandWrapper +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/elements/City.kt b/Programming/ProgLab6/general/src/main/kotlin/elements/City.kt new file mode 100644 index 0000000..adf4c3f --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/elements/City.kt @@ -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 { + + 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 + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/elements/CityBuilder.kt b/Programming/ProgLab6/general/src/main/kotlin/elements/CityBuilder.kt new file mode 100644 index 0000000..0f71627 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/elements/CityBuilder.kt @@ -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 = 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 + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/elements/Climate.kt b/Programming/ProgLab6/general/src/main/kotlin/elements/Climate.kt new file mode 100644 index 0000000..d2ec6ad --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/elements/Climate.kt @@ -0,0 +1,6 @@ +package elements + +/** + * Перечисление возможных климатов для города [City]. + */ +enum class Climate {RAIN_FOREST, MONSOON, HUMIDCONTINENTAL, SUBARCTIC, TUNDRA} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/elements/Coordinates.kt b/Programming/ProgLab6/general/src/main/kotlin/elements/Coordinates.kt new file mode 100644 index 0000000..23aeb45 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/elements/Coordinates.kt @@ -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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/elements/Government.kt b/Programming/ProgLab6/general/src/main/kotlin/elements/Government.kt new file mode 100644 index 0000000..59d7021 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/elements/Government.kt @@ -0,0 +1,6 @@ +package elements + +/** + * Перечисления возможных видов правительства для города [City]. + */ +enum class Government{ ARISTOCRACY, ANARCHY, KLEPTOCRACY, CORPORATOCRACY, JUNTA} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/elements/Human.kt b/Programming/ProgLab6/general/src/main/kotlin/elements/Human.kt new file mode 100644 index 0000000..c87e79b --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/elements/Human.kt @@ -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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/CollectionHasNoElementException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/CollectionHasNoElementException.kt new file mode 100644 index 0000000..00a7fa6 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/CollectionHasNoElementException.kt @@ -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" +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/CommandNotFoundException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/CommandNotFoundException.kt new file mode 100644 index 0000000..2834bf5 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/CommandNotFoundException.kt @@ -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 не найдена" +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/ConnectionException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/ConnectionException.kt new file mode 100644 index 0000000..a3be8e5 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/ConnectionException.kt @@ -0,0 +1,5 @@ +package exceptions + +class ConnectionException(): Exception() { + override val message: String = "Не удалось установить соединение." +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/InvalidAmountOfArgumentsException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/InvalidAmountOfArgumentsException.kt new file mode 100644 index 0000000..47fb872 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/InvalidAmountOfArgumentsException.kt @@ -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" +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/InvalidElementValueException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/InvalidElementValueException.kt new file mode 100644 index 0000000..254bc91 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/InvalidElementValueException.kt @@ -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' не может быть задано" +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/NoNextCommandException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/NoNextCommandException.kt new file mode 100644 index 0000000..74fc801 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/NoNextCommandException.kt @@ -0,0 +1,16 @@ +package exceptions + +/** + * Исключение, показывающее, что очередь на выполнение команд пуста. + * + * Наследуется от класса [Exception]. + * + * @property message Сообщение об ошибке типа [String]. + * + * @constructor Стандартный конструктор класса [Exception]. + * + * @since 1.0 + */ +class NoNextCommandException: Exception() { + override val message: String = "Чтение скрипта окончено" +} \ No newline at end of file diff --git a/Programming/ProgLab6/general/src/main/kotlin/exceptions/ProgramExitException.kt b/Programming/ProgLab6/general/src/main/kotlin/exceptions/ProgramExitException.kt new file mode 100644 index 0000000..b546d91 --- /dev/null +++ b/Programming/ProgLab6/general/src/main/kotlin/exceptions/ProgramExitException.kt @@ -0,0 +1,16 @@ +package exceptions + +/** + * Исключение, показывающее, что программа завершила работу. + * + * Наследуется от класса [Exception]. + * + * @property message Сообщение об ошибке типа [String]. + * + * @constructor Стандартный конструктор класса [Exception]. + * + * @since 1.0 + */ +class ProgramExitException(): Exception() { + override val message: String = "Завершение работы." +} \ No newline at end of file diff --git a/Programming/ProgLab6/gradle.properties b/Programming/ProgLab6/gradle.properties new file mode 100644 index 0000000..7fc6f1f --- /dev/null +++ b/Programming/ProgLab6/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official diff --git a/Programming/ProgLab6/gradle/wrapper/gradle-wrapper.jar b/Programming/ProgLab6/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..1b33c55 Binary files /dev/null and b/Programming/ProgLab6/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Programming/ProgLab6/gradle/wrapper/gradle-wrapper.properties b/Programming/ProgLab6/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ca025c8 --- /dev/null +++ b/Programming/ProgLab6/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/Programming/ProgLab6/gradlew b/Programming/ProgLab6/gradlew new file mode 100644 index 0000000..23d15a9 --- /dev/null +++ b/Programming/ProgLab6/gradlew @@ -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" "$@" diff --git a/Programming/ProgLab6/gradlew.bat b/Programming/ProgLab6/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/Programming/ProgLab6/gradlew.bat @@ -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 diff --git a/Programming/ProgLab6/server/build.gradle b/Programming/ProgLab6/server/build.gradle new file mode 100644 index 0000000..45f1bcb --- /dev/null +++ b/Programming/ProgLab6/server/build.gradle @@ -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' + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/Server.kt b/Programming/ProgLab6/server/src/main/kotlin/Server.kt new file mode 100644 index 0000000..00a2fd9 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/Server.kt @@ -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() +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/AddCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/AddCommand.kt new file mode 100644 index 0000000..b30313e --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/AddCommand.kt @@ -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) { + 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 "Добавляет элемент в коллекцию" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/ClearCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/ClearCommand.kt new file mode 100644 index 0000000..ea99963 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/ClearCommand.kt @@ -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) { + super.execute(arguments) + ci.cm.clearCollection() + result = "Коллекция отчищена.\n" + ci.io.logger.info("Коллекция отчищена.") + } + + override fun describe(): String { + return "Удаляет все элементы коллекции" + } + + override fun getName(): String { + return "clear" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/CountGreaterThenMetersAboveSeaLevelCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/CountGreaterThenMetersAboveSeaLevelCommand.kt new file mode 100644 index 0000000..81f7b7c --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/CountGreaterThenMetersAboveSeaLevelCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/GroupCountingByNameCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/GroupCountingByNameCommand.kt new file mode 100644 index 0000000..1adcf6f --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/GroupCountingByNameCommand.kt @@ -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) { + super.execute(arguments) + val names: HashMap = 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/InfoCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/InfoCommand.kt new file mode 100644 index 0000000..b0766ac --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/InfoCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/PingCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/PingCommand.kt new file mode 100644 index 0000000..774b7d6 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/PingCommand.kt @@ -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) { + super.execute(arguments) + result = "ping\n" + } + + override fun getName(): String { + return "ping" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/PrintFieldAscendingGovernmentCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/PrintFieldAscendingGovernmentCommand.kt new file mode 100644 index 0000000..aa7b296 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/PrintFieldAscendingGovernmentCommand.kt @@ -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) { + super.execute(arguments) + val governments: List = 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveByIdCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveByIdCommand.kt new file mode 100644 index 0000000..0c977fa --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveByIdCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveGreaterCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveGreaterCommand.kt new file mode 100644 index 0000000..5dff448 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveGreaterCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveLastCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveLastCommand.kt new file mode 100644 index 0000000..123023d --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/RemoveLastCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/ReorderCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/ReorderCommand.kt new file mode 100644 index 0000000..ef74bc0 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/ReorderCommand.kt @@ -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) { + super.execute(arguments) + ci.cm.reorderElements() + result = "Коллекция успешно перевёрнута.\n" + ci.io.logger.info("Коллекция перевернута.") + } + + override fun describe(): String { + return "Переворачивает коллекцию" + } + + override fun getName(): String { + return "reorder" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/SaveCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/SaveCommand.kt new file mode 100644 index 0000000..2265869 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/SaveCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/ShowCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/ShowCommand.kt new file mode 100644 index 0000000..a67c75f --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/ShowCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/commands/UpdateCommand.kt b/Programming/ProgLab6/server/src/main/kotlin/commands/UpdateCommand.kt new file mode 100644 index 0000000..8d24bbc --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/commands/UpdateCommand.kt @@ -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) { + 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" + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/core/CollectionManager.kt b/Programming/ProgLab6/server/src/main/kotlin/core/CollectionManager.kt new file mode 100644 index 0000000..3109247 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/core/CollectionManager.kt @@ -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 = Stack() + 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 = Stack() + 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 { + 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 { + io.logger.info("Группировка элементов по именам...") + val names: HashMap = 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() + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/core/CommandInvoker.kt b/Programming/ProgLab6/server/src/main/kotlin/core/CommandInvoker.kt new file mode 100644 index 0000000..cf747d1 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/core/CommandInvoker.kt @@ -0,0 +1,74 @@ +package core + +import commands.* +import exceptions.CommandNotFoundException + +class CommandInvoker(val cm: CollectionManager): CommandInvokerInterface { + val commands: HashMap = 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 { + val list = mutableListOf() + for (command in commands.values) { + val wrapper = CommandWrapper() + wrapper.wrapCommand(command) + list.add(wrapper) + } + io.logger.info("Подготовлен список команд.") + return list.toList() + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/core/ConnectionReceiver.kt b/Programming/ProgLab6/server/src/main/kotlin/core/ConnectionReceiver.kt new file mode 100644 index 0000000..f31de93 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/core/ConnectionReceiver.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/core/ServerInitializer.kt b/Programming/ProgLab6/server/src/main/kotlin/core/ServerInitializer.kt new file mode 100644 index 0000000..0e374f8 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/core/ServerInitializer.kt @@ -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("Завершение работы.") + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/server/src/main/kotlin/io/IOManager.kt b/Programming/ProgLab6/server/src/main/kotlin/io/IOManager.kt new file mode 100644 index 0000000..c4be048 --- /dev/null +++ b/Programming/ProgLab6/server/src/main/kotlin/io/IOManager.kt @@ -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 { + val reader = BufferedInputStream(FileInputStream(file)) + val text = reader.readAllBytes().decodeToString() + reader.close() + val decodedStack: Stack = Stack() + if (text != "") { + val decodedList: List = decodeFromString>(text) + for (element in decodedList) { + decodedStack.push(element) + } + } + return decodedStack + } + + fun writeJsonFile(file: String, stack: Stack) { + val listToEncode: List = stack.toList() + val text: String = encodeToString(listToEncode) + val writer = BufferedWriter(FileWriter(file)) + writer.write(text) + writer.close() + } + + fun readLocalCommands(): String { + return readln() + } +} \ No newline at end of file diff --git a/Programming/ProgLab6/settings.gradle b/Programming/ProgLab6/settings.gradle new file mode 100644 index 0000000..b75b507 --- /dev/null +++ b/Programming/ProgLab6/settings.gradle @@ -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' \ No newline at end of file