Supplier<Sequence<String>> cannot be iterated more than once

Masato Nagashima :

I want to receive HTTP request using Java web socket API in a Kotlin script.

I only need two lines in the request, so I tried to get the entire request as lines of String, iterate it, and get lines that match regex for each of the two line.

I tested with FileInputStream of a text file, instead of actual InputStream sent from client. I can iterate only once so I can't get the second line. I receive the following result.

GET /hello.html HTTP/1.1

Exception in thread "main" java.util.NoSuchElementException: Sequence contains no element matching the predicate. at RequestParam$filterLines$1.invoke(RequestParam.kt:29) at RequestParam$filterLines$1.invoke(RequestParam.kt:6) at RequestParam.(RequestParam.kt:19) at RequestParamKt.main(RequestParam.kt:26)

The official Kotlin reference says Sequence instantiated by asSequence() cannot be iterated more than once. So I used Supplier interface, but that doesn't work.

I tried the same thing before and it worked. I'm now rewriting the code since the previous version is kind of messy. The previous version is here

And below is the current version I'm struggling with.

import java.io.FileInputStream
import java.io.InputStream
import java.util.function.Supplier
import kotlin.streams.asSequence

class RequestParam(private val inputStream: InputStream) {
    val path: String
    val host: String

    init {
        //I'd like to receive request from client as multiple lines of String.
        //I generated Supplier since instance made by asSequence() cannot be iterated more than once https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/index.html
        val linesSupplier: Supplier<Sequence<String>> = Supplier { inputStream.bufferedReader().lines().asSequence() }

        //I only need lines starting with "GET" or "Host:"
        val regex = { x: String -> { y: String -> y.matches(Regex(x)) } }
        val regexGet = regex("GET.*")
        val regexHost = regex("Host:.*")

        //Iterate supplier and get the first line that matches each regex
        val filterLines = { x: Sequence<String>, y: (String) -> Boolean -> x.first(y) }
        path = filterLines(linesSupplier.get(), regexGet)
        println(path) //works fine
        host = filterLines(linesSupplier.get(), regexHost)
        println(host) // no good
    }
}

fun main(args: Array<String>) {
    //Test with some file
    val inputStream = FileInputStream("C:\\path\\to\\my\\test.txt")
    val requestParam = RequestParam(inputStream)
}

and the text file I used in the test

GET /hello.html HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.barfoo.com
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive

Why can't I iterate linesSupplier for more than once?

Utkan Ozyurek :

Sequence type that represents lazily evaluated collections. Top-level functions for instantiating sequences and extension functions for sequences.

For the solution without suplier: as the documentation says sequence try to lazily evaluate the values from stream that is returned by 'lines' method of the inputStream. so for the first call it will read the stream, and second call try to do the same which will fail because you can not read the stream twice. you can solve it with using toList instead of asSequence. List will read it once and keep it in memory. if you are not limited with memory it will solve your problem.

val linesSupplier: List<String> = inputStream.bufferedReader().lines().toList()

EDIT: For the solution with supplier: you should be able to make it work by moving the creation of file stream into supplier. In your code segment supplier fails since you are using the same stream that is consumed already.

val linesSupplier: Supplier<Sequence<String>> = Supplier { FileInputStream("C:\\test.txt").bufferedReader().lines().asSequence() }

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=145644&siteId=1
Recommended