summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/io/SourceReader.scala
blob: f5be1da58aef44c5562bca774c5517673df9455c (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                                          
                           






























































































































                                                                            
/*     ____ ____  ____ ____  ______                                     *\
**    / __// __ \/ __// __ \/ ____/    SOcos COmpiles Scala             **
**  __\_ \/ /_/ / /__/ /_/ /\_ \       (c) 2002-2006, LAMP/EPFL         **
** /_____/\____/\___/\____/____/                                        **
\*                                                                      */

// $Id$


package scala.tools.nsc.io;


import java.io.{File, FileInputStream, IOException};
import java.nio.{ByteBuffer, CharBuffer};
import java.nio.channels.{FileChannel, ReadableByteChannel};
import java.nio.charset.{CharsetDecoder, CoderResult};

/** This class implements methods to read and decode source files. */
class SourceReader(decoder: CharsetDecoder) {

  import SourceReader.{decode, flush}

  //########################################################################
  // Private Fields

  /** The input byte buffer (small enough to fit in cache) */
  private val bytes: ByteBuffer = ByteBuffer.allocate(0x4000);

  /** The output character buffer */
  private var chars: CharBuffer = CharBuffer.allocate(0x4000);

  //########################################################################
  // Public Methods

  /** Reads the file with the specified name. */
  def read(filename: String): Array[Char]= read(new File(filename));

  /** Reads the specified file. */
  def read(file: File): Array[Char] = {
    val channel: FileChannel = new FileInputStream(file).getChannel();
    try {
      read(channel);
    } finally {
      channel.close();
    }
  }

  /** Reads the specified file. */
  def read(file: AbstractFile): Array[Char] = {
    val decoder: CharsetDecoder = this.decoder.reset();
    val bytes: ByteBuffer = ByteBuffer.wrap(file.read);
    val chars: CharBuffer = this.chars; chars.clear();
    terminate(flush(decoder, decode(decoder, bytes, chars, true)));
  }

  /** Reads the specified byte channel. */
  def read(input: ReadableByteChannel): Array[Char] = {
    val decoder: CharsetDecoder = this.decoder.reset();
    val bytes: ByteBuffer = this.bytes; bytes.clear();
    var chars: CharBuffer = this.chars; chars.clear();
    var endOfInput: Boolean = false
    while (!endOfInput ) {
      endOfInput = input.read(bytes) < 0;
      bytes.flip();
      chars = decode(decoder, bytes, chars, endOfInput);
    }
    terminate(flush(decoder, chars));
  }

  //########################################################################
  // Private Methods

  /**
   * Sets the specified char buffer as the new output buffer and
   * reads and returns its content.
   */
  private def terminate(chars: CharBuffer): Array[Char] = {
    val result = new Array[Char](chars.length());
    chars.get(result);
    this.chars = chars;
    result;
  }

}

object SourceReader {

  /**
   * Decodes the content of the specified byte buffer with the
   * specified decoder into the specified char buffer, allocating
   * bigger ones if necessary, then compacts the byte buffer and
   * returns the last allocated char buffer. The "endOfInput"
   * argument indicates whether the byte buffer contains the last
   * chunk of the input file.
   */
  def decode(decoder: CharsetDecoder, bytes: ByteBuffer, chars: CharBuffer,
             endOfInput: boolean): CharBuffer =
  {
    val result: CoderResult = decoder.decode(bytes, chars, endOfInput);
    if (result.isUnderflow()) {
      bytes.compact();
      chars;
    } else {
      if (result.isError()) throw new IOException(result.toString());
      assert(result.isOverflow());
      decode(decoder, bytes, increaseCapacity(chars), endOfInput);
    }
  }

  /**
   * Flushes the specified decoder into the specified char buffer,
   * allocating bigger ones if necessary and then flips and returns
   * the last allocated char buffer.
   */
  def flush(decoder: CharsetDecoder, chars: CharBuffer): CharBuffer = {
    val result: CoderResult = decoder.flush(chars);
    if (result.isUnderflow()) {
      chars.flip();
      chars;
    } else {
      if (result.isError()) throw new IOException(result.toString());
      assert(result.isOverflow());
      flush(decoder, increaseCapacity(chars));
    }
  }

  /**
   * Flips the specified buffer and returns a new one with the same
   * content but with an increased capacity.
   */
  private def increaseCapacity(buffer: CharBuffer): CharBuffer = {
    buffer.flip();
    val capacity = 2 * buffer.capacity();
    CharBuffer.allocate(capacity).put(buffer);
  }

}