( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

自動的に閉じるSource

テキストファイルをオープンして内容を出力する - ブログタイトル入力 を読んで。


自分ならLoanPattern使ってこう書くかな?

type Closable = { def close():Unit }

def using[A <: Closable,B]( resource:A )( f:A => B ) = try {
   f(resource) 
} finally { resource.close }


こんなかんじで使う。

scala> using( Source.fromFile("/Users/ozaki/testdata.txt") ){ _.getLines.mkString }
res1: String = aaabbbccc


行指向で処理させたいならこう。

type HasLines = { def getLines () : Iterator[String] }

def eachLines[ A <: Closable with HasLines]( resource:A )( f:String => Unit) = using( resource ){ _.getLines.foreach(f) }


こう使うわけですが、

scala> eachLines(Source.fromFile("/Users/ozaki/testdata.txt") ){ s => println(s) }
aaa
bbb
ccc


他に、Iteratorに対してfilterやらmapやらやりたい場合もあって、そもそもSource自身が最後まで読んだらcloseすればいんじゃね?って思ったので、自動的に閉じるSourceを書いた。

yuroyoro-util/src/main/scala/com/yuroyoro/util/io/AutoCloseSource.scala at master · yuroyoro/yuroyoro-util · GitHub

package com.yuroyoro.util.io

import java.io.{ InputStream, BufferedReader, InputStreamReader, FileInputStream, PrintStream, File => JFile }
import java.net.{ URI, URL }
import scala.io.{Source, BufferedSource, Codec}

/**
 * InputStreamの終端まで読んだら自動的にcloseするSource
 */
class AutoCloseSource(inputStream: InputStream,  bufferSize: Int)(override implicit val codec: Codec) extends BufferedSource (inputStream, bufferSize)(codec){
  private var closed = false

  def closed_? = closed

  override val iter = {
    val reader = bufferedReader()
    new Iterator[Int] {
      def hasNext = true
      def next = {
        val c = codec wrap reader.read()
        if( c == -1 && closed == false) close()
        c
      }
    } takeWhile (_ != -1) map (_.toChar)
  }

  override def close() {
    super.close()
    closed = true
  }
}

object AutoCloseSource {

  val DefaultBufSize = scala.io.Source.DefaultBufSize

  def fromFile(name: String)(implicit codec: Codec): AutoCloseSource =
    fromFile(new JFile(name))(codec)

  def fromFile(name: String, enc: String): AutoCloseSource =
    fromFile(name)(Codec(enc))

  def fromFile(uri: URI)(implicit codec: Codec): AutoCloseSource =
    fromFile(new JFile(uri))(codec)

  def fromFile(uri: URI, enc: String): AutoCloseSource =
    fromFile(uri)(Codec(enc))

  def fromFile(file: JFile)(implicit codec: Codec): AutoCloseSource =
    fromFile(file, Source.DefaultBufSize)(codec)

  def fromFile(file: JFile, enc: String): AutoCloseSource =
    fromFile(file)(Codec(enc))

  def fromFile(file: JFile, enc: String, bufferSize: Int): AutoCloseSource =
    fromFile(file, bufferSize)(Codec(enc))

  def fromFile(file: JFile, bufferSize: Int)(implicit codec: Codec): AutoCloseSource = {
    val inputStream = new FileInputStream(file)

    createAutoCloseSource(
      inputStream,
      bufferSize,
      () => fromFile(file, bufferSize)(codec),
      () => inputStream.close()
    )(codec) withDescription ("file:" + file.getAbsolutePath)
  }

  def fromURI(uri: URI)(implicit codec: Codec): AutoCloseSource =
    fromFile(new JFile(uri))(codec)

  def fromURL(s: String, enc: String): AutoCloseSource =
    fromURL(s)(Codec(enc))

  def fromURL(s: String)(implicit codec: Codec): AutoCloseSource =
    fromURL(new URL(s))(codec)

  def fromURL(url: URL, enc: String): AutoCloseSource =
    fromURL(url)(Codec(enc))

  def fromURL(url: URL)(implicit codec: Codec): AutoCloseSource =
    fromInputStream(url.openStream())(codec)

  def fromInputStream(is: InputStream, enc: String): AutoCloseSource =
    fromInputStream(is)(Codec(enc))

  def fromInputStream(is: InputStream)(implicit codec: Codec): AutoCloseSource =
    createAutoCloseSource(is, reset = () => fromInputStream(is)(codec), close = () => is.close())(codec)

  def createAutoCloseSource(
    inputStream: InputStream,
    bufferSize: Int = DefaultBufSize,
    reset: () => Source = null,
    close: () => Unit = null
  )(implicit codec: Codec): AutoCloseSource = {
    // workaround for default arguments being unable to refer to other parameters
    val resetFn = if (reset == null) () => createAutoCloseSource(inputStream, bufferSize, reset, close)(codec) else reset

    new AutoCloseSource(inputStream, bufferSize)(codec) withReset resetFn withClose close
  }
}


これは、持ってるInputStreamがEOF返したら自動的にcloseする。これでLoanPatternとかでwrapする関数使わなくてもclose忘れがなくなるよね。まぁサイズの決まってる入力にしか使えないけど。

scala> import com.yuroyoro.util.io._
import com.yuroyoro.util.io._

scala> val as = AutoCloseSource.fromFile("/Users/ozaki/testdata.txt")
as: com.yuroyoro.util.io.AutoCloseSource = non-empty iterator

scala> as.getLines.mkStringres0: String = aaabbbccc

scala> as.closed_?
res1: Boolean = true

scala> as.getLines.mkString
res2: String =