Removed global singletons, renamed functions

This commit is contained in:
ZhigalskiiIvan 2023-03-30 00:59:02 +03:00
parent ef7b49b301
commit 0bc2c199d6

View File

@ -6,33 +6,30 @@ import org.jetbrains.letsPlot.intern.Plot
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
object StatisticBuilder { /** Singleton object which gives statistics of saved texts.
/** */
* Singleton object which gives statistics of saved texts. class StatisticBuilder {
/** Recognizes the name of the text, which
* user want to see statistics about and type of output.
* It calls methods of graphic building or printing data in console,
* build required for it objects.
*/ */
fun getStatistics(textData: TextData) {
if (!textData.haveText()) {
fun askAndExecuteSelfCommands() {
/**
* Recognizes the name of the text, which
* user want to see statistics about and type of output.
* It calls methods of graphic building or printing data in console,
* build required for it objects.
*/
if (!TextData.haveText()) {
println("No saved texts") println("No saved texts")
return return
} }
val text = TextData.getTextData("Input name of a text which you want to see statistics about.") val text = textData.getTextData("Input name of a text which you want to see statistics about.")
var counter = 1 var counter = 1
val wordsCountsMap = text.getSentencesList().map { counter++ to it.getWordsCount() } val wordsCountsMap = text.getSentencesList().map { counter++ to it.getWordsCount() }
println("Print \"console\" if you have see data in console, \"graphic\" if you have see histogram and \"both\" if you have see them together:") println("Print \"console\" if you have see data in console, \"graphic\" if you have see histogram and \"both\" if you have see them together:")
val request = Main.requestInput(listOf("console", "graphic", "both")) val request = requestInput(listOf("console", "graphic", "both"))
request.first.exe() request.first.exe()
when (request.second) { when (request.second) {
@ -47,13 +44,11 @@ object StatisticBuilder {
} }
/** It builds bar chart with data of mapOfSentenceNumToItsSize.
* textName: name of texts, which user want to see statistics about.
* mapOfSentenceNumToItsSize: map of pairs of sentence numbers and their words count.
*/
private fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) { private fun buildGraphic(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
/**
* It builds bar chart with data of mapOfSentenceNumToItsSize.
* textName: name of texts, which user want to see statistics about.
* mapOfSentenceNumToItsSize: map of pairs of sentence numbers and their words count.
*/
val plot: Plot = val plot: Plot =
ggplot(mapOfSentenceNumToItsSize) + ggsize(1000, 600) + geomBar { x = "sentence number"; y = "words count" } ggplot(mapOfSentenceNumToItsSize) + ggsize(1000, 600) + geomBar { x = "sentence number"; y = "words count" }
@ -62,13 +57,13 @@ object StatisticBuilder {
} }
/**
* Prints statistics according to data from mapOfSentenceNumToItsSize.
*
* textName: name of texts, which user want to see statistics about.
* mapOfSentenceNumToItsSize: map of pairs of sentence numbers and their words count.
*/
private fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) { private fun printStatisticsInConsole(textName: String, mapOfSentenceNumToItsSize: Map<Int, Int>) {
/**
* Prints statistics according to data from mapOfSentenceNumToItsSize.
*
* textName: name of texts, which user want to see statistics about.
* mapOfSentenceNumToItsSize: map of pairs of sentence numbers and their words count.
*/
val sectionTitle = "Text name: $textName" val sectionTitle = "Text name: $textName"
println("-".repeat(sectionTitle.length)) println("-".repeat(sectionTitle.length))
@ -96,43 +91,39 @@ object StatisticBuilder {
println("-".repeat(sectionTitle.length)) println("-".repeat(sectionTitle.length))
println("Done!\n") println("Done!\n")
} }
} }
object TextReader { /** Singleton object used for reading text from a file or console. */
/** Singleton object used for reading text from a file or console. */ class TextReader {
fun askAndExecuteSelfCommands() {
/**
* Asks the user which where they want to read the text from and
* calls special for file- and console- reading methods according the answer.
*/
/**
* Asks the user which where they want to read the text from and
* calls special for file- and console- reading methods according the answer.
*/
fun readNewText(textData: TextData) {
println("Where do you want to add the text: from the console or a file?") println("Where do you want to add the text: from the console or a file?")
val kindOfSource = Main.requestInput(listOf("console", "file")) val kindOfSource = requestInput(listOf("console", "file"))
kindOfSource.first.exe() kindOfSource.first.exe()
when (kindOfSource.second) { when (kindOfSource.second) {
"console" -> readFromConsole() "console" -> readFromConsole(textData)
"file" -> readFromFile() "file" -> readFromFile(textData)
} }
} }
private fun readFromConsole() { /** Read from console the name of text, checks that it hasn't saved yet and its contents,
/** * asks if entered text is not correct and re-calls itself or
* Read from console the name of text, checks that it hasn't saved yet and its contents, * calls method of adding received text to data.
* asks if entered text is not correct and re-calls itself or */
* calls method of adding received text to data. private fun readFromConsole(textData: TextData) {
*/
println("Input name of a text:") println("Input name of a text:")
if (TextData.haveText()) println("Unavailable(existing) names: ${TextData.getTextNamesInString()}.") if (textData.haveText()) println("Unavailable(existing) names: ${textData.getTextNamesInString()}.")
val nameRequest = Main.requestInput(unavailableInputs = TextData.getTextNamesList()) val nameRequest = requestInput(unavailableInputs = textData.getTextNamesList())
nameRequest.first.exe() nameRequest.first.exe()
val name = nameRequest.second.toString() val name = nameRequest.second.toString()
@ -157,21 +148,20 @@ object TextReader {
println("Was input data right?[yes, no]:") println("Was input data right?[yes, no]:")
val correctInputRequest = Main.requestInput(listOf("yes", "no")) val correctInputRequest = requestInput(listOf("yes", "no"))
correctInputRequest.first.exe() correctInputRequest.first.exe()
if (correctInputRequest.second == "yes") addTextToData(name, content) if (correctInputRequest.second == "yes") addTextToData(textData, name, content)
else readFromConsole() else readFromConsole(textData)
} }
private fun readFromFile() { /**
/** * Asks for the name of text, path to file to read text from,
* Asks for the name of text, path to file to read text from, * checks for its existing, reads contents and,
* checks for its existing, reads contents and, * asks if entered names is not correct and re-calls itself or
* asks if entered names is not correct and re-calls itself or * calls method of adding received text to data.
* calls method of adding received text to data. */
*/ private fun readFromFile(textData: TextData) {
println("Input a name of a text:") println("Input a name of a text:")
val name = readln() val name = readln()
@ -180,7 +170,7 @@ object TextReader {
val contentsFile: File val contentsFile: File
pathReadCycle@ while (true) { pathReadCycle@ while (true) {
val pathRequest = Main.requestInput<String>() val pathRequest = requestInput<String>()
pathRequest.first.exe() pathRequest.first.exe()
val filePath = pathRequest.second.toString() val filePath = pathRequest.second.toString()
@ -198,49 +188,41 @@ object TextReader {
val content = contentsFile.readText() val content = contentsFile.readText()
print("Input was correct?[yes, no]: ") print("Input was correct?[yes, no]: ")
val correctInputRequest = Main.requestInput(listOf("yes", "no")) val correctInputRequest = requestInput(listOf("yes", "no"))
correctInputRequest.first.exe() correctInputRequest.first.exe()
when (correctInputRequest.second) { when (correctInputRequest.second) {
"yes" -> addTextToData(name, content) "yes" -> addTextToData(textData, name, content)
"no" -> readFromFile() "no" -> readFromFile(textData)
} }
} }
/**
private fun addTextToData(textName: String, content: String) { * Calls method of TextData class to add new text in set
/** * of tracked texts.
* Calls method of TextData class to add new text in set * @param textName name of new text.
* of tracked texts. * @param content content of new text.
* textName: name of new text. */
* content: content of new text. private fun addTextToData(textData: TextData, textName: String, content: String) =
*/ textData.addNewText(textName, content)
TextData.addNewText(textName, content)
}
} }
/** Object used for storing data about tracking texts,
* includes methods for work with them.
*/
class TextData {
object TextData { /** list of monitored texts. */
/**
* Object used for storing data about tracking texts,
* includes methods for work with them.
* textsList: list of monitored texts.
*/
private val textsList = mutableListOf<Text>() private val textsList = mutableListOf<Text>()
fun haveText(): Boolean = textsList.isNotEmpty() fun haveText(): Boolean = textsList.isNotEmpty()
/** Method for removing text from watch list. Asks for a name of a text
* and if it's being tracked, remove it from the textsList.
*/
fun removeText() { fun removeText() {
/**
* Method for removing text from watch list. Asks for a name of a text
* and if it's being tracked, remove it from the textsList.
*/
if (!haveText()) { if (!haveText()) {
@ -254,70 +236,71 @@ object TextData {
println("Text ${removingText.getName()} removed.") println("Text ${removingText.getName()} removed.")
} }
/**
* Returns Text object whose name matches searchingName or null
* if there is no text with same name in the textsList.
* searchingName: name of the text to be found.
* return: the text with searchingName name or null.
*/
private fun getTextByName(searchingName: String): Text? { private fun getTextByName(searchingName: String): Text? {
/**
* Returns Text object whose name matches searchingName or null
* if there is no text with same name in the textsList.
* searchingName: name of the text to be found.
* return: the text with searchingName name or null.
*/
for (text in textsList) if (text.getName() == searchingName) return text for (text in textsList) if (text.getName() == searchingName) return text
return null return null
} }
/** @return string with names of tracking texts separated by delimiter. */
fun getTextNamesInString(delimiter: String = ", "): String { fun getTextNamesInString(delimiter: String = ", "): String {
/** Returns string with names of tracking texts separated by delimiter. */
return textsList.joinToString(delimiter) { it.getName() } return textsList.joinToString(delimiter) { it.getName() }
} }
/** @return list with names of tracking texts. */
fun getTextNamesList(): List<String> { fun getTextNamesList(): List<String> {
/** Returns list with names of tracking texts. */
return textsList.map { it.getName() } return textsList.map { it.getName() }
} }
/** Calls method of textAnalyzer for getting Text object from
* textName and content and adds it in textsList.
*/
fun addNewText(textName: String, content: String) { fun addNewText(textName: String, content: String) {
/**
* Calls method of textAnalyzer for getting Text object from
* textName and content and adds it in textsList.
*/
textsList.add(TextAnalyzer.getTextObjFromContents(textName, content)) textsList.add(TextAnalyzer.getTextObjFromContents(textName, content))
} }
/**
* Reads name of the text to be found and returns it text if
* it exists.
* @param message message which prints with calling this method.
* @return text
*/
fun getTextData(message: String = "Input name of a text"): Text { fun getTextData(message: String = "Input name of a text"): Text {
/**
* Reads name of the text to be found and returns it text if
* it exists.
* message: message which prints with calling this method.
* return: text
*/
println(message) println(message)
println("Saved texts: ${getTextNamesInString()}.") println("Saved texts: ${getTextNamesInString()}.")
val nameRequest = Main.requestInput(getTextNamesList()) val nameRequest = requestInput(getTextNamesList())
nameRequest.first.exe() nameRequest.first.exe()
return getTextByName(nameRequest.second.toString())!! return getTextByName(nameRequest.second.toString())!!
} }
/** Object used for getting text from the string information. */
object TextAnalyzer { object TextAnalyzer {
/** Object used for getting text from the string information. */
private val DELIMITERS = Regex("[!?.]+\\s+") private val DELIMITERS = Regex("[!?.]+\\s+")
private val WHITESPACES = Regex("\\s+") private val WHITESPACES = Regex("\\s+")
private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?") private val WHITESPACES_OR_EMPTY = Regex("(\\s+)?")
/**
* Receives text as input and splits it by sentence, calculate its lengths,
* create list of Sentence objects and returns Text object, created with
* this list, input field name, and count of sentences in content.
*/
fun getTextObjFromContents(name: String, content: String): Text { fun getTextObjFromContents(name: String, content: String): Text {
/**
* Receives text as input and splits it by sentence, calculate its lengths,
* create list of Sentence objects and returns Text object, created with
* this list, input field name, and count of sentences in content.
*/
var sentencesCount = 1 var sentencesCount = 1
val listOfSentences = mutableListOf<Text.Sentence>() val listOfSentences = mutableListOf<Text.Sentence>()
@ -340,20 +323,20 @@ object TextData {
} }
/** Stores information about conjugated text. */
data class Text( data class Text(
private val name: String, private val name: String,
private val sentencesCount: Int, private val sentencesCount: Int,
private val sentencesList: List<Sentence>, private val sentencesList: List<Sentence>,
) { ) {
/** Stores information about conjugated text. */
fun getName() = name fun getName() = name
fun getSentencesList() = sentencesList fun getSentencesList() = sentencesList
/** Stores information about count of words. */
data class Sentence(private val wordsCount: Int) { data class Sentence(private val wordsCount: Int) {
/** stores information about count of words. */
fun getWordsCount() = wordsCount fun getWordsCount() = wordsCount
} }
} }
@ -361,196 +344,195 @@ object TextData {
} }
/** Function used for exiting out of program. */
fun exit(): Nothing { fun exit(): Nothing {
/** function used for exiting out of program. */
println("bye!") println("bye!")
exitProcess(0) exitProcess(0)
} }
object CommandCenter { /** Singleton used for reading commands from console and storing data about
/** * the functions they should execute.
* Singleton used for reading commands from console and storing data about */
* the functions they should execute.
class CommandCenter(
private val textData: TextData,
private val textReader: TextReader,
private val statisticBuilder: StatisticBuilder
) {
private val exitCommand = Command("exit", ::exit)
private val addCommand = Command("add text") { textReader.readNewText(textData) }
private val showStatisticsCommand = Command("show statistics") { statisticBuilder.getStatistics(textData) }
private val removeTextCommand = Command("Remove text") { textData.removeText() }
private val commandsList = listOf(exitCommand, addCommand, showStatisticsCommand, removeTextCommand)
val commandsNames = commandsList.map { it.name }
/** Enumerating of available commands names and functions conjugated
* with them.
*/ */
class Command(val name: String, val executingFun: () -> Unit)
private enum class Commands(val executingFun: () -> Unit) { /** Prints list of names of available commands, requests the name and
/** * returns corresponding to entered name function.
* Enumerating of available commands names and functions conjugated */
* with them.
*/
EXIT(::exit),
ADD_TEXT({ TextReader.askAndExecuteSelfCommands() }),
SHOW_STATISTICS({ StatisticBuilder.askAndExecuteSelfCommands() }),
REMOVE_TEXT({ TextData.removeText() })
}
fun readCommandFromConsole(): () -> Unit { fun readCommandFromConsole(): () -> Unit {
/**
* Prints list of names of available commands, requests the name and
* returns corresponding to entered name function.
*/
println("Input one of the commands: ${Commands.values().joinToString { it.name }}:")
val commandNameRequest = Main.requestInput(Commands.values().map { it.name }) println("Input one of the commands: ${commandsNames.joinToString()}:")
val commandNameRequest = requestInput(commandsNames)
commandNameRequest.first.exe() commandNameRequest.first.exe()
val funName = commandNameRequest.second.toString() val funName = commandNameRequest.second.toString()
return Commands.valueOf(funName).executingFun return commandsList.find { it.name == funName }!!.executingFun
} }
} }
/** Interface used to existing commands objects, whose exe value
* stores function which calls after request due the program.
*/
interface InputOutcomeCommand { interface InputOutcomeCommand {
/** /** Function returned in a Main.requestInput method for returned to
* Interface used to existing commands objects, whose exe value * main menu of application or continuation of the process.
* stores function which calls after request due the program.
* exe: function returned in a Main.requestInput method for returned to
* main menu of application or continuation of the process.
*/ */
val exe: () -> Unit val exe: () -> Unit
} }
object ContinueCommand : InputOutcomeCommand { /** If called exe of this object, program continue executing
/** If called exe of this object, program continue executing * without changes.
* without changes. */
*/ class ContinueCommand : InputOutcomeCommand {
override val exe: () -> Unit = {} override val exe: () -> Unit = {}
} }
object ReturnCommand : InputOutcomeCommand { /**
/** * If called exe of this object, a custom ReturnException is thrown
* If called exe of this object, a custom ReturnException is thrown * and process of executing some called command interrupted. Exception
* and process of executing some called command interrupted. Exception * catches in mainCycle and program command request is repeated.
* catches in mainCycle and program command request is repeated. */
*/ class ReturnCommand : InputOutcomeCommand {
override val exe: () -> Unit = { override val exe: () -> Unit = throw ReturnException()
throw ReturnException()
}
}
class ReturnException : Exception() {
/** Custom exception being thrown if user want to return to the menu. */
}
class InvalidInputTypeException : Exception() {
/**
* Custom exception being thrown if input can't be converted
* to the requested type.
*/
}
class InvalidElemInInputException : Exception() {
/**
* Custom exception being thrown if input doesn't match the list
* of possible values.
*/
} }
object Main { /** Custom exception being thrown if user want to return to the menu. */
/** class ReturnException : Exception()
* Object which contains request processing method and working body of
* application.
*/
fun workCycle() { /** Custom exception being thrown if input can't be converted
/** * to the requested type.
* Method repeating the process of calling functions returned by */
* CommandCenter. If ReturnException was thrown, it is caught here, class InvalidInputTypeException : Exception()
* last iteration breaks and new one is called.
*/ /** Custom exception being thrown if input doesn't match the list
mainCycle@ while (true) { * of possible values.
try { */
(CommandCenter.readCommandFromConsole())() class InvalidElemInInputException : Exception()
} catch (e: ReturnException) {
println("You have been returned in main menu.")
continue@mainCycle /** Method repeating the process of calling functions returned by
} * CommandCenter. If ReturnException was thrown, it is caught here,
* last iteration breaks and new one is called.
*/
fun workCycle(commandCenter: CommandCenter) {
mainCycle@ while (true) {
try {
(commandCenter.readCommandFromConsole())()
} catch (e: ReturnException) {
println("You have been returned in main menu.")
continue@mainCycle
} }
} }
}
inline fun <reified T> requestInput(
availableInputs: List<T>? = null,
unavailableInputs: List<T>? = null
): Pair<InputOutcomeCommand, Any> {
/**
* Method reads from console input, check if it isn't available, repeat the request from the console
* in this case and convert input in type T if possible, else throws an exception and repeat the
* request.
*
* availableInputs: list of available values, the choice among which
* is requested from the console at some stage of the program.
* unavailableInputs: list of unavailable values.
* return: a pair of function, which could be called after the request from this function to return
* if user want it or continue and value that the user has selected from the list of available.
*/
var inputT: Any /** Method reads from console input, check if it isn't available, repeat the request from the console
* in this case and convert input in type T if possible, else throws an exception and repeat the
* request.
*
* @param availableInputs list of available values, the choice among which
* is requested from the console at some stage of the program.
* @param unavailableInputs list of unavailable values.
* @return a pair of function, which could be called after the request from this function to return
* if user want it or continue and value that the user has selected from the list of available.
*/
inline fun <reified T> requestInput(
availableInputs: List<T>? = null,
unavailableInputs: List<T>? = null
): Pair<InputOutcomeCommand, Any> {
readingAndChangingTypeCycle@ while (true) { var inputT: Any
val input = readln().trim() readingAndChangingTypeCycle@ while (true) {
if (input == "return") return Pair(ReturnCommand, "")
try { val input = readln().trim()
inputT = when (typeOf<T>()) { if (input == "return") return Pair(ReturnCommand(), "")
typeOf<Int>() -> input.toInt()
typeOf<Double>() -> input.toDouble()
typeOf<Boolean>() -> input.toBoolean()
typeOf<Byte>() -> input.toByte()
typeOf<Long>() -> input.toLong()
typeOf<Float>() -> input.toFloat()
typeOf<Char>() -> {
if (input.trim().length == 1) {
input.trim().toCharArray()[0]
} else throw InvalidInputTypeException()
}
typeOf<String>() -> input try {
else -> throw InvalidInputTypeException() // inputT = input to typeOf<T>()
inputT = when (typeOf<T>()) {
typeOf<Int>() -> input.toInt()
typeOf<Double>() -> input.toDouble()
typeOf<Boolean>() -> input.toBoolean()
typeOf<Byte>() -> input.toByte()
typeOf<Long>() -> input.toLong()
typeOf<Float>() -> input.toFloat()
typeOf<Char>() -> {
if (input.trim().length == 1) {
input.trim().toCharArray()[0]
} else throw InvalidInputTypeException()
} }
return when { typeOf<String>() -> input
availableInputs != null && unavailableInputs != null -> { else -> throw InvalidInputTypeException()
if (inputT.toString() in availableInputs.map { it.toString() } &&
inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand, inputT)
else throw InvalidElemInInputException()
}
availableInputs != null -> {
if (inputT.toString() in availableInputs.map { it.toString() }) Pair(ContinueCommand, inputT)
else throw InvalidElemInInputException()
}
unavailableInputs != null -> {
if (inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand, inputT)
else throw InvalidElemInInputException()
}
else -> Pair(ContinueCommand, inputT)
}
} catch (e: NumberFormatException) {
continue@readingAndChangingTypeCycle
} catch (e: InvalidInputTypeException) {
continue@readingAndChangingTypeCycle
} catch (e: InvalidElemInInputException) {
println(
"There isn't this elem in list of available inputs. " +
"Try to repeat or enter return to exit in main menu: "
)
continue@readingAndChangingTypeCycle
} }
return when {
availableInputs != null && unavailableInputs != null -> {
if (inputT.toString() in availableInputs.map { it.toString() } &&
inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand(), inputT)
else throw InvalidElemInInputException()
}
availableInputs != null -> {
if (inputT.toString() in availableInputs.map { it.toString() }) Pair(ContinueCommand(), inputT)
else throw InvalidElemInInputException()
}
unavailableInputs != null -> {
if (inputT.toString() !in unavailableInputs.map { it.toString() }) Pair(ContinueCommand(), inputT)
else throw InvalidElemInInputException()
}
else -> Pair(ContinueCommand(), inputT)
}
} catch (e: NumberFormatException) {
continue@readingAndChangingTypeCycle
} catch (e: InvalidInputTypeException) {
continue@readingAndChangingTypeCycle
} catch (e: InvalidElemInInputException) {
println(
"There isn't this elem in list of available inputs. " +
"Try to repeat or enter return to exit in main menu: "
)
continue@readingAndChangingTypeCycle
} }
} }
} }
fun main() { fun main() {
Main.workCycle()
} val textData = TextData()
val statisticBuilder = StatisticBuilder()
val textReader = TextReader()
val commandCenter = CommandCenter(textData, textReader, statisticBuilder)
workCycle(commandCenter)
}