Converting mutable java classes to immutable scala classes

539 Views Asked by At

I have a class which functions something like this

class OutputFile(name: String, index: Int = 0){
  val localFile = File.createTempFile(name, ".gz")
  localFile.deleteOnExit()
  val localFileOut =new FileOutputStream(localFile)
  private[this] var bytesWritted: Int = 0
  def write(line: Bytes): OutputFile = {
    if(bytesWritten > SOMESIZE) flush //this function uploads the file to a different location and closes the localfile and localFileOut
    try{
      writeLine(line) //this writes the line to the localfile
      this
    }catch{
      //If the file had been closed by flush create a new object
      case IOException => 
        val outFile = new OutputFile(name, index+1)
        outfile.write(line)
        outfile
    }
    //Other functions like flush and writeLine
  }

However now I cannot use this object with an immutable. Coming from a java background it is difficult for me to convert this class to an immutable style. In java code we could have just used a global variable for the output streams and changed it whenever required.

Is there some better method that I am definitely missing to implement such a scenario.

1

There are 1 best solutions below

3
On BEST ANSWER

To make the workflow of OutputFile immutable, every call has to return a new immutable instance, not just the call that swaps outfiles:

object OutputFile {
  def apply(name: String): OutputFile = {
    new OutputFile(name, newStream(name), 0)
  }

  private def newStream(name: String): FileOutputStream = {
    val localFile = File.createTempFile(name, ".gz")
    localFile.deleteOnExit()
    new FileOutputStream(localFile)
  }
}

class OutputFile(name: String, stream: FileOutputStream, bytesWritten: Int) {

  val THRESHOLD = 1000000

  def write(line: Bytes): OutputFile = write(line, 1)

  private def write(line: Bytes, attempt: Int): OutputFile = {
    try {
      val written = writeLine(line) //this writes the line to the localfile
      if (written > THRESHOLD) {
        flush
        new OutputFile(name, OutputFile.newStream(name), 0)
      } else new OutputFile(name, stream, written)
    } catch {
      case x: IOException =>
        if (attempt > 3) throw x
        else write(line, attempt + 1)
    }
  }

  // returns bytesWritten + length of line
  private def writeLine(line: Bytes): Int = ???

  // uploads file, closes and deletes it
  private def flush: Unit = ???
}

I've added a companion object OutputFile so that the actual constructor is the constructor for the new immutable instance and to abstract the opening of a new stream.

Each call to write creates a new OutputFile and tracks how many bytes have been written to the current file. Once the THRESHOLD has been reached, the file is flushed and a new instance with a new stream is returned. The IOException is no longer responsible for triggering the new file (that's just done when we know we've flushed), instead it's a retry with a max of 3 attempts.

A final word of caution: This class is still inherently stateful, since it does deal with File I/O. While this tries to pretend there is no state, it makes the assumption that write will never be called twice on a single instance.