All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lt.saltyjuice.dragas.chatty.v3.irc.adapter.IrcAdapter.kt Maven / Gradle / Ivy

package lt.saltyjuice.dragas.chatty.v3.irc.adapter

import lt.saltyjuice.dragas.chatty.v3.core.adapter.Adapter
import lt.saltyjuice.dragas.chatty.v3.irc.message.Request
import lt.saltyjuice.dragas.chatty.v3.irc.message.Response

/**
 * IRC message adapter.
 *
 * Should be implemented in accordance to RFC2812 section 2.3 and be able to deserialize
 * [ ":" prefix SPACE ] command [ params ] crlf syntax messages.
 */
open class IrcAdapter : Adapter()
{
    override fun serialize(any: Response): String
    {
        val sb = StringBuilder()
        sb.append(any.command)
        any.arguments.forEach {
            sb.append(" $it")
        }
        if (!sb.isBlank())
            sb.append("\r\n")
        val serializedChildren = any.otherResponses.
                parallelStream()
                .map(this::serialize)
                .filter {
                    it.isNotEmpty()
                }
                .collect(
                        {
                            StringBuilder()
                        },
                        { supplier: StringBuilder, element: String ->
                            supplier.append(element)
                        },
                        { supplier: StringBuilder, element: StringBuilder ->
                            supplier.append(element.toString())
                        })
        sb.append(serializedChildren)
        return sb.toString()
    }

    override fun deserialize(block: String): Request
    {
        val request = Request(block)
        val metadata = splitMetaData(block)
        request.prefixes = extractPrefix(metadata[0])
        request.arguments = extractArguments(metadata[2])
        request.command = metadata[1]
        return request
    }

    /**
     * Splits message data according to [ ":" prefix SPACE ] command [ params ] syntax.
     *
     * The main idea here is that optional groups are separated with spaces (RFC 1459 section 2.3.1 and
     * RFC 2812 section 2.3.1).
     *
     * @param block message provided by server to be decoded.
     * @return a list which is split in up to 3 groups: prefix, command and params. Particular spots are empty strings, when unavailable.
     */
    protected open fun splitMetaData(block: String): List
    {
        //There seems to be a discrepancy between RFC 1459 and RFC 2812, where there's an additional
        // *14 symbol in parameters definition. The inverse of this (14*) would be repetition which
        // ISO 14977 states. Currently this method falls back to RFC 1459 definition.
        val split = block.split(" ", ignoreCase = true, limit = 2) // splits splits into prefix and (command [params]) or command [params]
        val nullableList = ArrayList(split)
        if (split[0].startsWith(":"))
        {
            nullableList.remove(split[1])
            nullableList.addAll(split[1].split(" ", limit = 2))
        }
        if (nullableList.size < 3)
        {
            //It is possible that there's only command available, thus padding must be added
            nullableList.add(0, "")
            if (nullableList.size < 3) // padding for arguments
                nullableList.add("")
        }
        return nullableList
    }

    /**
     * In accordance to RFC 2812 section 2.3.1 prefix syntax is "servername / ( nickname [ [ "!" user ] "@" host ] )",
     *  which is part of optional group [ ":" prefix SPACE ]
     * therefore this particular method tries to extract the prefix part from metadata.
     *
     * This part is used to determine origin of request in question.
     * @param prefixContainer a block of string extracted when splitting metadata from actual parameters
     * @return a list of strings, with up to 3 parameters:
     * * First one is either nickname/servername. Only available if [prefixContainer] starts with ":" (colon character)
     * * Second parameter is user. Only available if prefix isn't only server name and "!" (exclamation mark)
     * character is available in [prefixContainer].
     * * Third parameter is hostname. Only available if [prefixContainer] contains "@" (at sign) character.
     *
     * If a parameter is unavailable, querying the list returns an empty string for that parameter.
     */
    protected open fun extractPrefix(prefixContainer: String): List
    {
        val extractedData = ArrayList(3)
        extractedData.addAll(arrayOf("", "", ""))
        // prefix must start with ":" as it is part of the optional,
        // group. If it doesn't, assume that there's no prefix.
        if (!prefixContainer.startsWith(":"))
            return extractedData
        val exclamationIndex = prefixContainer.indexOf("!")
        val atIndex = prefixContainer.indexOf("@")
        //since @ will always be available it has to be checked for first
        if (atIndex == -1)
        {
            extractedData[0] = prefixContainer // means that prefix is only hostname
        }                                                       // also replaces start of line
        else
        {
            val nicknameAndUser = prefixContainer.substring(0, atIndex)
            if (exclamationIndex != -1)
            {
                extractedData[0] = nicknameAndUser.substring(0, exclamationIndex)
                extractedData[1] = nicknameAndUser.substring(exclamationIndex + 1)
            }
            extractedData[2] = prefixContainer.substring(atIndex + 1)
        }
        extractedData[0] = extractedData[0].replace(":", "")
        return extractedData
    }

    /**
     * Extracts arguments from message according to RFC 2813 syntax:
     * *14( SPACE middle ) [ SPACE ":" trailing ] / 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
     *
     * @param arguments a 3rd returned value from [splitMetaData]
     * @return a list of arguments split by " "
     */
    protected open fun extractArguments(arguments: String): List
    {
        val separateLongArgument = arguments.split(Regex(" ?:"), 2) // always returns 2, even if one of them is empty
        val actualArguments = separateLongArgument[0].split(" ")
        val returnable = ArrayList(actualArguments)
        if (separateLongArgument.size > 1)
            returnable.add(separateLongArgument[1])
        returnable.removeAll { it.isEmpty() }
        return returnable
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy