Reputation: 3
I am writing some tests for a function which loads a CSV via a function readIntoList
using a BufferSource.
class CSV(source: BufferedSource) {
def readIntoList: List[List[Any]] = {
lazy val lines = Using.resource(source) { x => x.getLines.zipWithIndex }
println(source)
val columns = for {
(line, index: Int) <- lines
} yield {
val cols = line.split(",").map(_.trim)
val row = cols(0)
if (index == 0) List("Index", row)
else List(index, row)
}
columns.toList
}
}
Loading the source with a string works fine.
val singleRowCSV =
"""TotalSpend
|111
|""".stripMargin
val csv = new CSV(
new BufferedSource(new ByteArrayInputStream(singleRowCSV.getBytes()))
)
However calling the function using Source.fromURL
and providing a CSV on the file system.
val resource = getClass.getResource("/10_rows.csv")
val csv = new CSV(Source.fromURL(resource))
Causes java.io.IOException: Stream closed
It was my understanding that Using will automatically close the stream for me, so now I am wondering where the stream is being closed?
Upvotes: 0
Views: 366
Reputation: 27595
That's what Using
do - it's the equivalent of Java's try with resources:
try (Resource resource = initialize()) {
// use resource
} // once you leave this block resource is guaranteed to be .close()ed
This is basically a solution to make sure .close()
is always called once you exit the block, whether you just return a value normally or throw exception.
If you don't want your resources to be closed here, don't wrap them with Using
here, but in the place that should be responsible for managing the resource's lifecycle.
In your code you have:
Using.resource(source) { x => x.getLines.zipWithIndex }
which means that resource is closed once you leave this block. However, .getLines
might be evaluated lazily so when you start evaluating it once the stream is closed you'll get an exception, so you can try making the call eager to avoid it:
// easier, but I wouldn't recommend that for large files
Using.resource(source) { x => x.getLines.zipWithIndex.toList } // closed here
or you can just perform whole operation inside Using
:
def readIntoList: List[List[Any]] = {
// actually you should acquire resource here as well so I would suggest
// source: () => BufferedSource
Using.resource(source()) { resource =>
lazy val lines = resource.getLines.zipWithIndex
println(resource)
val columns = for {
(line, index: Int) <- lines
} yield {
val cols = line.split(",").map(_.trim)
val row = cols(0)
if (index == 0) List("Index", row)
else List(index, row)
}
columns.toList
} // closed here
}
Upvotes: 1