Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

In an attempt to begin learning Scala, I've taken a stab at creating a simple wrapper around Java's File & FileInputStream in order to read lines from a file. This functionality already exists in the Source class and is just a learning exercise.

If you were building this wrapper in Scala, is there anything you might change? Are there any best practices I'm overlooking?

////////// Driver.scala ////////////////

object Driver {

  def main(args: Array[String]): Unit = {
    val uri = File.classpathResource("sample.txt")
    val fileReader = FileReader.open(uri)
    println("Reading File: " + fileReader.file.name() )
    for( val line <- fileReader.lines() ) {
      println(line)
    }
    fileReader.close()
  }

}

////////// File.scala ////////////////

import java.io.{File => JavaFile}
import java.net.URI

/** This object provides convenience methods for working with local file resources */
object File {

  /** Helper method to resolve a URI for a resource on the classpath
   *
   * @param the resource's path name
   * @return the resource's URI
   */
  def classpathResource(pathName: String): URI = {
    val url = this.getClass().getClassLoader().getResource(pathName)
    url.toURI()
  }

}

/** This class represents a file on the file system */
class File(val javaFile: JavaFile) {

  def this(pathName: String) = this(new JavaFile(pathName))
  def this(uri: URI) = this(new JavaFile(uri))  

  /** Get the file's file name */
  def name(): String = javaFile.getName()

  /** Get the file's absolute path */
  def path(): String = javaFile.getAbsolutePath()

  /** Open a new file reader. Call FileReader.close() once you have finished
   *  using the file reader.
   *  
   *  @return a new file reader instance
   *  @throws FileNotFoundException if the file cannot be opened for reading
   */
  def openReader(): FileReader = {
    new FileReader(this)
  }

}

////////// FileReader.scala ////////////////

import java.io.FileInputStream
import java.net.URI

/** This object provides convenience methods for reading files */
object FileReader {

  /** Creates and opens a FileReader class instance for a given file's path name
   * 
   * @param the file's path name
   * @return an instance of the File class
   * @throws FileNotFoundException if the file cannot be opened for reading
   */
  def open(pathName: String): FileReader = this open new File(pathName)

  /** Creates and opens a FileReader class instance for a given file's URI
   * 
   * @param the file's URI
   * @return an instance of the File class
   * @throws FileNotFoundException if the file cannot be opened for reading
   */
  def open(uri: URI): FileReader = this open new File(uri)

  /** Creates and opens a FileReader class instance for a given File instance
   * 
   * @param a File instance
   * @return an instance of the FileReader class
   * @throws FileNotFoundException if the file cannot be opened for reading
   */
  def open(file: File): FileReader = new FileReader(file)

}


class FileReader(val file: File) {

  private val inputStream = new FileInputStream(file.javaFile)

  private val lineIterator = new BufferedLineIterator(inputStream);

  /** Read the file's lines using a string iterator
   *  
   *  @return file's line iterator (Iterator[String])
   */
  def lines(): Iterator[String] = lineIterator

  /** Closes the file reader */
  def close(): Unit = inputStream.close()

}


////////// BufferedLineIterator.scala ////////////////

import scala.collection.Iterator
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.InputStream

class BufferedLineIterator(inputStream: InputStream) extends Iterator[String] {

  /** Buffered reader for reading lines from the file */
  private val bufferedReader: BufferedReader = {
    val inputStreamReader = new InputStreamReader(inputStream)
    new BufferedReader(inputStreamReader)
  }

  /** Stores the next line waiting to be returned from the next() method */
  private var nextLine: String = null

  /** Identifies if the file has another line to read
   * 
   * @return boolean identifying if the file has another line to read
   */
  override def hasNext(): Boolean = {
    if( nextLine == null ) {
      nextLine = bufferedReader.readLine
    }
    nextLine != null
  }

  /** Reads the next line from the file
   * 
   * @return the line
   */
  override def next(): String = {
    try {
      if(hasNext) {
        nextLine
      } else {
        Iterator.empty.next
      }
    } finally {
      nextLine = null
    }
  }

}
share|improve this question
    
Looks pretty good! The only big thing I found out is in the for-comprehension: don't use val there. In 2.10 it's use is forbidden. ;) –  sschaef Sep 10 '12 at 8:45

1 Answer 1

Looks ok. A Traversable is a more interesting way to do this, since you can then auto-close the resource. There's one thing I took an issue to:

private val inputStream = new FileInputStream(file.javaFile)

private val lineIterator = new BufferedLineIterator(inputStream);

def lines(): Iterator[String] = lineIterator

The first two being val means this cannot be reused. However, if you turn them into def, then lines will already return a valid iterator.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.